2 ==============================================================================
4 This file is part of the JUCE library - "Jules' Utility Class Extensions"
5 Copyright 2004-11 by Raw Material Software Ltd.
7 ------------------------------------------------------------------------------
9 JUCE can be redistributed and/or modified under the terms of the GNU General
10 Public License (Version 2), as published by the Free Software Foundation.
11 A copy of the license is included in the JUCE distribution, or can be found
12 online at www.gnu.org/licenses.
14 JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
15 WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 A PARTICULAR PURPOSE. See the GNU General Public License for more details.
18 ------------------------------------------------------------------------------
20 To release a closed-source product which uses JUCE, commercial licenses are
21 available: visit www.rawmaterialsoftware.com/juce for more information.
23 ==============================================================================
26 // (This file gets included by juce_win32_NativeCode.cpp, rather than being
27 // compiled on its own).
28 #if JUCE_INCLUDED_FILE
32 namespace CDReaderHelpers
35 #define FILE_ANY_ACCESS 0
36 #ifndef FILE_READ_ACCESS
37 #define FILE_READ_ACCESS 1
39 #ifndef FILE_WRITE_ACCESS
40 #define FILE_WRITE_ACCESS 2
43 #define METHOD_BUFFERED 0
44 #define IOCTL_SCSI_BASE 4
45 #define SCSI_IOCTL_DATA_OUT 0
46 #define SCSI_IOCTL_DATA_IN 1
47 #define SCSI_IOCTL_DATA_UNSPECIFIED 2
49 #define CTL_CODE2(DevType, Function, Method, Access) (((DevType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
50 #define IOCTL_SCSI_PASS_THROUGH_DIRECT CTL_CODE2( IOCTL_SCSI_BASE, 0x0405, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS )
51 #define IOCTL_SCSI_GET_ADDRESS CTL_CODE2( IOCTL_SCSI_BASE, 0x0406, METHOD_BUFFERED, FILE_ANY_ACCESS )
54 #define SRB_ENABLE_RESIDUAL_COUNT 0x04
55 #define SRB_DIR_IN 0x08
56 #define SRB_DIR_OUT 0x10
57 #define SRB_EVENT_NOTIFY 0x40
58 #define SC_HA_INQUIRY 0x00
59 #define SC_GET_DEV_TYPE 0x01
60 #define SC_EXEC_SCSI_CMD 0x02
61 #define SS_PENDING 0x00
74 READTYPE_READ_D4_1
= 7,
78 struct SCSI_PASS_THROUGH
86 UCHAR SenseInfoLength
;
88 ULONG DataTransferLength
;
90 ULONG DataBufferOffset
;
91 ULONG SenseInfoOffset
;
95 struct SCSI_PASS_THROUGH_DIRECT
103 UCHAR SenseInfoLength
;
105 ULONG DataTransferLength
;
108 ULONG SenseInfoOffset
;
112 struct SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER
114 SCSI_PASS_THROUGH_DIRECT spt
;
116 UCHAR ucSenseBuf
[32];
145 struct SRB_ExecSCSICmd
156 BYTE
*SRB_BufPointer
;
164 BYTE SenseArea
[SENSE_LEN
+ 2];
190 TOCTRACK tracks
[100];
195 //==============================================================================
196 struct CDDeviceDescription
198 CDDeviceDescription() : ha (0), tgt (0), lun (0), scsiDriveLetter (0)
202 void createDescription (const char* data
)
204 description
<< String (data
+ 8, 8).trim() // vendor
205 << ' ' << String (data
+ 16, 16).trim() // product id
206 << ' ' << String (data
+ 32, 4).trim(); // rev
211 char scsiDriveLetter
; // will be 0 if not using scsi
214 //==============================================================================
218 CDReadBuffer (const int numberOfFrames
)
219 : startFrame (0), numFrames (0), dataStartOffset (0),
220 dataLength (0), bufferSize (2352 * numberOfFrames
), index (0),
221 buffer (bufferSize
), wantsIndex (false)
225 bool isZero() const noexcept
227 for (int i
= 0; i
< dataLength
; ++i
)
228 if (buffer
[dataStartOffset
+ i
] != 0)
234 int startFrame
, numFrames
, dataStartOffset
;
235 int dataLength
, bufferSize
, index
;
236 HeapBlock
<BYTE
> buffer
;
240 class CDDeviceHandle
;
242 //==============================================================================
246 CDController() : initialised (false) {}
247 virtual ~CDController() {}
249 virtual bool read (CDReadBuffer
&) = 0;
250 virtual void shutDown() {}
252 bool readAudio (CDReadBuffer
& rb
, CDReadBuffer
* overlapBuffer
= 0);
256 CDDeviceHandle
* deviceInfo
;
257 int framesToCheck
, framesOverlap
;
260 void prepare (SRB_ExecSCSICmd
& s
);
261 void perform (SRB_ExecSCSICmd
& s
);
262 void setPaused (bool paused
);
266 //==============================================================================
270 CDDeviceHandle (const CDDeviceDescription
& device
, HANDLE scsiHandle_
)
271 : info (device
), scsiHandle (scsiHandle_
), readType (READTYPE_ANY
)
277 if (controller
!= nullptr)
279 controller
->shutDown();
284 CloseHandle (scsiHandle
);
287 bool readTOC (TOC
* lpToc
);
288 bool readAudio (CDReadBuffer
& buffer
, CDReadBuffer
* overlapBuffer
= 0);
289 void openDrawer (bool shouldBeOpen
);
290 void performScsiCommand (HANDLE event
, SRB_ExecSCSICmd
& s
);
292 CDDeviceDescription info
;
297 ScopedPointer
<CDController
> controller
;
299 bool testController (int readType
, CDController
* newController
, CDReadBuffer
& bufferToUse
);
302 //==============================================================================
303 HANDLE
createSCSIDeviceHandle (const char driveLetter
)
305 TCHAR devicePath
[] = { '\\', '\\', '.', '\\', driveLetter
, ':', 0, 0 };
306 DWORD flags
= GENERIC_READ
| GENERIC_WRITE
;
307 HANDLE h
= CreateFile (devicePath
, flags
, FILE_SHARE_WRITE
| FILE_SHARE_READ
, 0, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, 0);
309 if (h
== INVALID_HANDLE_VALUE
)
311 flags
^= GENERIC_WRITE
;
312 h
= CreateFile (devicePath
, flags
, FILE_SHARE_WRITE
| FILE_SHARE_READ
, 0, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, 0);
318 void findCDDevices (Array
<CDDeviceDescription
>& list
)
320 for (char driveLetter
= 'b'; driveLetter
<= 'z'; ++driveLetter
)
322 TCHAR drivePath
[] = { driveLetter
, ':', '\\', 0, 0 };
324 if (GetDriveType (drivePath
) == DRIVE_CDROM
)
326 HANDLE h
= createSCSIDeviceHandle (driveLetter
);
328 if (h
!= INVALID_HANDLE_VALUE
)
330 char buffer
[100] = { 0 };
332 SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER p
= { 0 };
333 p
.spt
.Length
= sizeof (SCSI_PASS_THROUGH
);
335 p
.spt
.SenseInfoLength
= 24;
336 p
.spt
.DataIn
= SCSI_IOCTL_DATA_IN
;
337 p
.spt
.DataTransferLength
= sizeof (buffer
);
338 p
.spt
.TimeOutValue
= 2;
339 p
.spt
.DataBuffer
= buffer
;
340 p
.spt
.SenseInfoOffset
= offsetof (SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER
, ucSenseBuf
);
344 DWORD bytesReturned
= 0;
346 if (DeviceIoControl (h
, IOCTL_SCSI_PASS_THROUGH_DIRECT
,
347 &p
, sizeof (p
), &p
, sizeof (p
),
348 &bytesReturned
, 0) != 0)
350 CDDeviceDescription dev
;
351 dev
.scsiDriveLetter
= driveLetter
;
352 dev
.createDescription (buffer
);
354 SCSI_ADDRESS scsiAddr
= { 0 };
355 scsiAddr
.Length
= sizeof (scsiAddr
);
357 if (DeviceIoControl (h
, IOCTL_SCSI_GET_ADDRESS
,
358 0, 0, &scsiAddr
, sizeof (scsiAddr
),
359 &bytesReturned
, 0) != 0)
361 dev
.ha
= scsiAddr
.PortNumber
;
362 dev
.tgt
= scsiAddr
.TargetId
;
363 dev
.lun
= scsiAddr
.Lun
;
374 DWORD
performScsiPassThroughCommand (SRB_ExecSCSICmd
* const srb
, const char driveLetter
,
375 HANDLE
& deviceHandle
, const bool retryOnFailure
)
377 SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER s
= { 0 };
378 s
.spt
.Length
= sizeof (SCSI_PASS_THROUGH
);
379 s
.spt
.CdbLength
= srb
->SRB_CDBLen
;
381 s
.spt
.DataIn
= (BYTE
) ((srb
->SRB_Flags
& SRB_DIR_IN
)
383 : ((srb
->SRB_Flags
& SRB_DIR_OUT
)
384 ? SCSI_IOCTL_DATA_OUT
385 : SCSI_IOCTL_DATA_UNSPECIFIED
));
387 s
.spt
.DataTransferLength
= srb
->SRB_BufLen
;
388 s
.spt
.TimeOutValue
= 5;
389 s
.spt
.DataBuffer
= srb
->SRB_BufPointer
;
390 s
.spt
.SenseInfoOffset
= offsetof (SCSI_PASS_THROUGH_DIRECT_WITH_BUFFER
, ucSenseBuf
);
392 memcpy (s
.spt
.Cdb
, srb
->CDBByte
, srb
->SRB_CDBLen
);
394 srb
->SRB_Status
= SS_ERR
;
395 srb
->SRB_TargStat
= 0x0004;
397 DWORD bytesReturned
= 0;
399 if (DeviceIoControl (deviceHandle
, IOCTL_SCSI_PASS_THROUGH_DIRECT
,
400 &s
, sizeof (s
), &s
, sizeof (s
), &bytesReturned
, 0) != 0)
402 srb
->SRB_Status
= SS_COMP
;
404 else if (retryOnFailure
)
406 const DWORD error
= GetLastError();
408 if ((error
== ERROR_MEDIA_CHANGED
) || (error
== ERROR_INVALID_HANDLE
))
410 if (error
!= ERROR_INVALID_HANDLE
)
411 CloseHandle (deviceHandle
);
413 deviceHandle
= createSCSIDeviceHandle (driveLetter
);
415 return performScsiPassThroughCommand (srb
, driveLetter
, deviceHandle
, false);
419 return srb
->SRB_Status
;
423 //==============================================================================
424 // Controller types..
426 class ControllerType1
: public CDController
431 bool read (CDReadBuffer
& rb
)
433 if (rb
.numFrames
* 2352 > rb
.bufferSize
)
438 s
.SRB_Flags
= SRB_DIR_IN
| SRB_EVENT_NOTIFY
;
439 s
.SRB_BufLen
= rb
.bufferSize
;
440 s
.SRB_BufPointer
= rb
.buffer
;
443 s
.CDBByte
[3] = (BYTE
) ((rb
.startFrame
>> 16) & 0xFF);
444 s
.CDBByte
[4] = (BYTE
) ((rb
.startFrame
>> 8) & 0xFF);
445 s
.CDBByte
[5] = (BYTE
) (rb
.startFrame
& 0xFF);
446 s
.CDBByte
[8] = (BYTE
) (rb
.numFrames
& 0xFF);
447 s
.CDBByte
[9] = (BYTE
) (deviceInfo
->readType
== READTYPE_ATAPI1
? 0x10 : 0xF0);
450 if (s
.SRB_Status
!= SS_COMP
)
453 rb
.dataLength
= rb
.numFrames
* 2352;
454 rb
.dataStartOffset
= 0;
459 //==============================================================================
460 class ControllerType2
: public CDController
469 BYTE bufPointer
[] = { 0, 0, 0, 8, 83, 0, 0, 0, 0, 0, 8, 0 };
473 s
.SRB_Flags
= SRB_EVENT_NOTIFY
| SRB_ENABLE_RESIDUAL_COUNT
;
475 s
.SRB_BufPointer
= bufPointer
;
486 s
.SRB_Status
= SS_ERR
;
488 if (deviceInfo
->readType
== READTYPE_READ10_2
)
490 BYTE bufPointer1
[] = { 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 9, 48, 35, 6, 0, 0, 0, 0, 0, 128 };
491 BYTE bufPointer2
[] = { 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 9, 48, 1, 6, 32, 7, 0, 0, 0, 0 };
493 for (int i
= 0; i
< 2; ++i
)
496 s
.SRB_Flags
= SRB_EVENT_NOTIFY
;
498 s
.SRB_BufPointer
= (i
== 0) ? bufPointer1
: bufPointer2
;
505 if (s
.SRB_Status
!= SS_COMP
)
511 BYTE bufPointer
[] = { 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 9, 48 };
514 s
.SRB_Flags
= SRB_EVENT_NOTIFY
;
516 s
.SRB_BufPointer
= bufPointer
;
523 return s
.SRB_Status
== SS_COMP
;
526 bool read (CDReadBuffer
& rb
)
528 if (rb
.numFrames
* 2352 > rb
.bufferSize
)
533 initialised
= init();
541 s
.SRB_Flags
= SRB_DIR_IN
| SRB_EVENT_NOTIFY
;
542 s
.SRB_BufLen
= rb
.bufferSize
;
543 s
.SRB_BufPointer
= rb
.buffer
;
546 s
.CDBByte
[1] = (BYTE
) (deviceInfo
->info
.lun
<< 5);
547 s
.CDBByte
[3] = (BYTE
) ((rb
.startFrame
>> 16) & 0xFF);
548 s
.CDBByte
[4] = (BYTE
) ((rb
.startFrame
>> 8) & 0xFF);
549 s
.CDBByte
[5] = (BYTE
) (rb
.startFrame
& 0xFF);
550 s
.CDBByte
[8] = (BYTE
) (rb
.numFrames
& 0xFF);
553 if (s
.SRB_Status
!= SS_COMP
)
556 rb
.dataLength
= rb
.numFrames
* 2352;
557 rb
.dataStartOffset
= 0;
562 //==============================================================================
563 class ControllerType3
: public CDController
568 bool read (CDReadBuffer
& rb
)
570 if (rb
.numFrames
* 2352 > rb
.bufferSize
)
581 s
.SRB_Flags
= SRB_DIR_IN
| SRB_EVENT_NOTIFY
;
582 s
.SRB_BufLen
= rb
.numFrames
* 2352;
583 s
.SRB_BufPointer
= rb
.buffer
;
586 s
.CDBByte
[3] = (BYTE
) ((rb
.startFrame
>> 16) & 0xFF);
587 s
.CDBByte
[4] = (BYTE
) ((rb
.startFrame
>> 8) & 0xFF);
588 s
.CDBByte
[5] = (BYTE
) (rb
.startFrame
& 0xFF);
589 s
.CDBByte
[9] = (BYTE
) (rb
.numFrames
& 0xFF);
592 if (s
.SRB_Status
!= SS_COMP
)
595 rb
.dataLength
= rb
.numFrames
* 2352;
596 rb
.dataStartOffset
= 0;
601 //==============================================================================
602 class ControllerType4
: public CDController
609 BYTE bufPointer
[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 48 };
613 s
.SRB_Flags
= SRB_EVENT_NOTIFY
;
616 s
.SRB_BufPointer
= bufPointer
;
622 return s
.SRB_Status
== SS_COMP
;
625 bool read (CDReadBuffer
& rb
)
627 if (rb
.numFrames
* 2352 > rb
.bufferSize
)
634 if (deviceInfo
->readType
== READTYPE_READ_D4_1
)
642 s
.SRB_Flags
= SRB_DIR_IN
| SRB_EVENT_NOTIFY
;
643 s
.SRB_BufLen
= rb
.bufferSize
;
644 s
.SRB_BufPointer
= rb
.buffer
;
647 s
.CDBByte
[3] = (BYTE
) ((rb
.startFrame
>> 16) & 0xFF);
648 s
.CDBByte
[4] = (BYTE
) ((rb
.startFrame
>> 8) & 0xFF);
649 s
.CDBByte
[5] = (BYTE
) (rb
.startFrame
& 0xFF);
650 s
.CDBByte
[8] = (BYTE
) (rb
.numFrames
& 0xFF);
653 if (s
.SRB_Status
!= SS_COMP
)
656 rb
.dataLength
= rb
.numFrames
* 2352;
657 rb
.dataStartOffset
= 0;
663 //==============================================================================
664 void CDController::prepare (SRB_ExecSCSICmd
& s
)
667 s
.SRB_Cmd
= SC_EXEC_SCSI_CMD
;
668 s
.SRB_HaID
= deviceInfo
->info
.ha
;
669 s
.SRB_Target
= deviceInfo
->info
.tgt
;
670 s
.SRB_Lun
= deviceInfo
->info
.lun
;
671 s
.SRB_SenseLen
= SENSE_LEN
;
674 void CDController::perform (SRB_ExecSCSICmd
& s
)
676 s
.SRB_PostProc
= CreateEvent (0, TRUE
, FALSE
, 0);
678 deviceInfo
->performScsiCommand (s
.SRB_PostProc
, s
);
681 void CDController::setPaused (bool paused
)
685 s
.SRB_Flags
= SRB_EVENT_NOTIFY
;
688 s
.CDBByte
[8] = (BYTE
) (paused
? 0 : 1);
692 bool CDController::readAudio (CDReadBuffer
& rb
, CDReadBuffer
* overlapBuffer
)
694 if (overlapBuffer
!= nullptr)
696 const bool canDoJitter
= (overlapBuffer
->bufferSize
>= 2352 * framesToCheck
);
697 const bool doJitter
= canDoJitter
&& ! overlapBuffer
->isZero();
700 && overlapBuffer
->startFrame
> 0
701 && overlapBuffer
->numFrames
> 0
702 && overlapBuffer
->dataLength
> 0)
704 const int numFrames
= rb
.numFrames
;
706 if (overlapBuffer
->startFrame
== (rb
.startFrame
- framesToCheck
))
708 rb
.startFrame
-= framesOverlap
;
710 if (framesToCheck
< framesOverlap
711 && numFrames
+ framesOverlap
<= rb
.bufferSize
/ 2352)
712 rb
.numFrames
+= framesOverlap
;
716 overlapBuffer
->dataLength
= 0;
717 overlapBuffer
->startFrame
= 0;
718 overlapBuffer
->numFrames
= 0;
727 const int checkLen
= framesToCheck
* 2352;
728 const int maxToCheck
= rb
.dataLength
- checkLen
;
730 if (overlapBuffer
->dataLength
== 0 || overlapBuffer
->isZero())
733 BYTE
* const p
= overlapBuffer
->buffer
+ overlapBuffer
->dataStartOffset
;
736 for (int i
= 0; i
< maxToCheck
; ++i
)
738 if (memcmp (p
, rb
.buffer
+ i
, checkLen
) == 0)
741 rb
.dataStartOffset
= i
;
743 rb
.startFrame
= overlapBuffer
->startFrame
+ framesToCheck
;
749 rb
.numFrames
= rb
.dataLength
/ 2352;
750 rb
.dataLength
= 2352 * rb
.numFrames
;
758 memcpy (overlapBuffer
->buffer
,
759 rb
.buffer
+ rb
.dataStartOffset
+ 2352 * (rb
.numFrames
- framesToCheck
),
760 2352 * framesToCheck
);
762 overlapBuffer
->startFrame
= rb
.startFrame
+ rb
.numFrames
- framesToCheck
;
763 overlapBuffer
->numFrames
= framesToCheck
;
764 overlapBuffer
->dataLength
= 2352 * framesToCheck
;
765 overlapBuffer
->dataStartOffset
= 0;
769 overlapBuffer
->startFrame
= 0;
770 overlapBuffer
->numFrames
= 0;
771 overlapBuffer
->dataLength
= 0;
780 int CDController::getLastIndex()
786 s
.SRB_Flags
= SRB_DIR_IN
| SRB_EVENT_NOTIFY
;
787 s
.SRB_BufLen
= sizeof (qdata
);
788 s
.SRB_BufPointer
= (BYTE
*) qdata
;
791 s
.CDBByte
[1] = (BYTE
) (deviceInfo
->info
.lun
<< 5);
793 s
.CDBByte
[3] = 1; // get current position
795 s
.CDBByte
[8] = (BYTE
) sizeof (qdata
);
798 return s
.SRB_Status
== SS_COMP
? qdata
[7] : 0;
801 //==============================================================================
802 bool CDDeviceHandle::readTOC (TOC
* lpToc
)
804 SRB_ExecSCSICmd s
= { 0 };
805 s
.SRB_Cmd
= SC_EXEC_SCSI_CMD
;
806 s
.SRB_HaID
= info
.ha
;
807 s
.SRB_Target
= info
.tgt
;
808 s
.SRB_Lun
= info
.lun
;
809 s
.SRB_Flags
= SRB_DIR_IN
| SRB_EVENT_NOTIFY
;
810 s
.SRB_BufLen
= 0x324;
811 s
.SRB_BufPointer
= (BYTE
*) lpToc
;
812 s
.SRB_SenseLen
= 0x0E;
814 s
.SRB_PostProc
= CreateEvent (0, TRUE
, FALSE
, 0);
820 performScsiCommand (s
.SRB_PostProc
, s
);
821 return (s
.SRB_Status
== SS_COMP
);
824 void CDDeviceHandle::performScsiCommand (HANDLE event
, SRB_ExecSCSICmd
& s
)
827 DWORD status
= performScsiPassThroughCommand ((SRB_ExecSCSICmd
*) &s
, info
.scsiDriveLetter
, scsiHandle
, true);
829 if (status
== SS_PENDING
)
830 WaitForSingleObject (event
, 4000);
835 bool CDDeviceHandle::readAudio (CDReadBuffer
& buffer
, CDReadBuffer
* overlapBuffer
)
839 testController (READTYPE_ATAPI2
, new ControllerType1(), buffer
)
840 || testController (READTYPE_ATAPI1
, new ControllerType1(), buffer
)
841 || testController (READTYPE_READ10_2
, new ControllerType2(), buffer
)
842 || testController (READTYPE_READ10
, new ControllerType2(), buffer
)
843 || testController (READTYPE_READ_D8
, new ControllerType3(), buffer
)
844 || testController (READTYPE_READ_D4
, new ControllerType4(), buffer
)
845 || testController (READTYPE_READ_D4_1
, new ControllerType4(), buffer
);
850 if (controller
!= nullptr && controller
->readAudio (buffer
, overlapBuffer
))
852 if (buffer
.wantsIndex
)
853 buffer
.index
= controller
->getLastIndex();
861 void CDDeviceHandle::openDrawer (bool shouldBeOpen
)
865 if (controller
!= nullptr)
867 controller
->shutDown();
868 controller
= nullptr;
873 CloseHandle (scsiHandle
);
878 SRB_ExecSCSICmd s
= { 0 };
879 s
.SRB_Cmd
= SC_EXEC_SCSI_CMD
;
880 s
.SRB_HaID
= info
.ha
;
881 s
.SRB_Target
= info
.tgt
;
882 s
.SRB_Lun
= info
.lun
;
883 s
.SRB_SenseLen
= SENSE_LEN
;
884 s
.SRB_Flags
= SRB_DIR_IN
| SRB_EVENT_NOTIFY
;
886 s
.SRB_BufPointer
= 0;
889 s
.CDBByte
[1] = (BYTE
) (info
.lun
<< 5);
890 s
.CDBByte
[4] = (BYTE
) (shouldBeOpen
? 2 : 3);
891 s
.SRB_PostProc
= CreateEvent (0, TRUE
, FALSE
, 0);
893 performScsiCommand (s
.SRB_PostProc
, s
);
896 bool CDDeviceHandle::testController (const int type
, CDController
* const newController
, CDReadBuffer
& rb
)
898 controller
= newController
;
899 readType
= (BYTE
) type
;
901 controller
->deviceInfo
= this;
902 controller
->framesToCheck
= 1;
903 controller
->framesOverlap
= 3;
906 memset (rb
.buffer
, 0xcd, rb
.bufferSize
);
908 if (controller
->read (rb
))
911 int* p
= (int*) (rb
.buffer
+ rb
.dataStartOffset
);
914 for (int i
= rb
.dataLength
/ 4; --i
>= 0;)
916 if (*p
++ == (int) 0xcdcdcdcd)
933 controller
->shutDown();
934 controller
= nullptr;
941 //==============================================================================
942 struct CDDeviceWrapper
944 CDDeviceWrapper (const CDDeviceDescription
& device
, HANDLE scsiHandle
)
945 : deviceHandle (device
, scsiHandle
), overlapBuffer (3), jitter (false)
947 // xxx jitter never seemed to actually be enabled (??)
950 CDDeviceHandle deviceHandle
;
951 CDReadBuffer overlapBuffer
;
955 //==============================================================================
956 int getAddressOfTrack (const TOCTRACK
& t
) noexcept
958 return (((DWORD
) t
.addr
[0]) << 24) + (((DWORD
) t
.addr
[1]) << 16)
959 + (((DWORD
) t
.addr
[2]) << 8) + ((DWORD
) t
.addr
[3]);
962 const int samplesPerFrame
= 44100 / 75;
963 const int bytesPerFrame
= samplesPerFrame
* 4;
964 const int framesPerIndexRead
= 4;
968 //==============================================================================
969 StringArray
AudioCDReader::getAvailableCDNames()
971 using namespace CDReaderHelpers
;
974 Array
<CDDeviceDescription
> list
;
975 findCDDevices (list
);
977 for (int i
= 0; i
< list
.size(); ++i
)
980 if (list
[i
].scsiDriveLetter
> 0)
981 s
<< String::charToString (list
[i
].scsiDriveLetter
).toUpperCase() << ": ";
983 s
<< list
[i
].description
;
990 AudioCDReader
* AudioCDReader::createReaderForCD (const int deviceIndex
)
992 using namespace CDReaderHelpers
;
994 Array
<CDDeviceDescription
> list
;
995 findCDDevices (list
);
997 if (isPositiveAndBelow (deviceIndex
, list
.size()))
999 HANDLE h
= createSCSIDeviceHandle (list
[deviceIndex
].scsiDriveLetter
);
1001 if (h
!= INVALID_HANDLE_VALUE
)
1002 return new AudioCDReader (new CDDeviceWrapper (list
[deviceIndex
], h
));
1008 AudioCDReader::AudioCDReader (void* handle_
)
1009 : AudioFormatReader (0, "CD Audio"),
1011 indexingEnabled (false),
1013 firstFrameInBuffer (0),
1016 using namespace CDReaderHelpers
;
1017 jassert (handle_
!= nullptr);
1019 refreshTrackLengths();
1021 sampleRate
= 44100.0;
1024 usesFloatingPointData
= false;
1026 buffer
.setSize (4 * bytesPerFrame
, true);
1029 AudioCDReader::~AudioCDReader()
1031 using namespace CDReaderHelpers
;
1032 CDDeviceWrapper
* const device
= static_cast <CDDeviceWrapper
*> (handle
);
1036 bool AudioCDReader::readSamples (int** destSamples
, int numDestChannels
, int startOffsetInDestBuffer
,
1037 int64 startSampleInFile
, int numSamples
)
1039 using namespace CDReaderHelpers
;
1040 CDDeviceWrapper
* const device
= static_cast <CDDeviceWrapper
*> (handle
);
1044 while (numSamples
> 0)
1046 const int bufferStartSample
= firstFrameInBuffer
* samplesPerFrame
;
1047 const int bufferEndSample
= bufferStartSample
+ samplesInBuffer
;
1049 if (startSampleInFile
>= bufferStartSample
1050 && startSampleInFile
< bufferEndSample
)
1052 const int toDo
= (int) jmin ((int64
) numSamples
, bufferEndSample
- startSampleInFile
);
1054 int* const l
= destSamples
[0] + startOffsetInDestBuffer
;
1055 int* const r
= numDestChannels
> 1 ? (destSamples
[1] + startOffsetInDestBuffer
) : nullptr;
1056 const short* src
= (const short*) buffer
.getData();
1057 src
+= 2 * (startSampleInFile
- bufferStartSample
);
1059 for (int i
= 0; i
< toDo
; ++i
)
1061 l
[i
] = src
[i
<< 1] << 16;
1064 r
[i
] = src
[(i
<< 1) + 1] << 16;
1067 startOffsetInDestBuffer
+= toDo
;
1068 startSampleInFile
+= toDo
;
1073 const int framesInBuffer
= buffer
.getSize() / bytesPerFrame
;
1074 const int frameNeeded
= (int) (startSampleInFile
/ samplesPerFrame
);
1076 if (firstFrameInBuffer
+ framesInBuffer
!= frameNeeded
)
1078 device
->overlapBuffer
.dataLength
= 0;
1079 device
->overlapBuffer
.startFrame
= 0;
1080 device
->overlapBuffer
.numFrames
= 0;
1081 device
->jitter
= false;
1084 firstFrameInBuffer
= frameNeeded
;
1087 CDReadBuffer
readBuffer (framesInBuffer
+ 4);
1088 readBuffer
.wantsIndex
= indexingEnabled
;
1091 for (i
= 5; --i
>= 0;)
1093 readBuffer
.startFrame
= frameNeeded
;
1094 readBuffer
.numFrames
= framesInBuffer
;
1096 if (device
->deviceHandle
.readAudio (readBuffer
, device
->jitter
? &device
->overlapBuffer
: 0))
1099 device
->overlapBuffer
.dataLength
= 0;
1104 buffer
.copyFrom (readBuffer
.buffer
+ readBuffer
.dataStartOffset
, 0, readBuffer
.dataLength
);
1105 samplesInBuffer
= readBuffer
.dataLength
>> 2;
1106 lastIndex
= readBuffer
.index
;
1110 int* l
= destSamples
[0] + startOffsetInDestBuffer
;
1111 int* r
= numDestChannels
> 1 ? (destSamples
[1] + startOffsetInDestBuffer
) : nullptr;
1113 while (--numSamples
>= 0)
1121 // sometimes the read fails for just the very last couple of blocks, so
1122 // we'll ignore and errors in the last half-second of the disk..
1123 ok
= startSampleInFile
> (trackStartSamples
[getNumTracks()] - 20000);
1132 bool AudioCDReader::isCDStillPresent() const
1134 using namespace CDReaderHelpers
;
1136 return static_cast <CDDeviceWrapper
*> (handle
)->deviceHandle
.readTOC (&toc
);
1139 void AudioCDReader::refreshTrackLengths()
1141 using namespace CDReaderHelpers
;
1142 trackStartSamples
.clear();
1143 zeromem (audioTracks
, sizeof (audioTracks
));
1147 if (static_cast <CDDeviceWrapper
*> (handle
)->deviceHandle
.readTOC (&toc
))
1149 int numTracks
= 1 + toc
.lastTrack
- toc
.firstTrack
;
1151 for (int i
= 0; i
<= numTracks
; ++i
)
1153 trackStartSamples
.add (samplesPerFrame
* getAddressOfTrack (toc
.tracks
[i
]));
1154 audioTracks
[i
] = ((toc
.tracks
[i
].ADR
& 4) == 0);
1158 lengthInSamples
= getPositionOfTrackStart (getNumTracks());
1161 bool AudioCDReader::isTrackAudio (int trackNum
) const
1163 return trackNum
>= 0 && trackNum
< getNumTracks() && audioTracks
[trackNum
];
1166 void AudioCDReader::enableIndexScanning (bool b
)
1168 indexingEnabled
= b
;
1171 int AudioCDReader::getLastIndex() const
1176 int AudioCDReader::getIndexAt (int samplePos
)
1178 using namespace CDReaderHelpers
;
1179 CDDeviceWrapper
* const device
= static_cast <CDDeviceWrapper
*> (handle
);
1181 const int frameNeeded
= samplePos
/ samplesPerFrame
;
1183 device
->overlapBuffer
.dataLength
= 0;
1184 device
->overlapBuffer
.startFrame
= 0;
1185 device
->overlapBuffer
.numFrames
= 0;
1186 device
->jitter
= false;
1188 firstFrameInBuffer
= 0;
1191 CDReadBuffer
readBuffer (4 + framesPerIndexRead
);
1192 readBuffer
.wantsIndex
= true;
1195 for (i
= 5; --i
>= 0;)
1197 readBuffer
.startFrame
= frameNeeded
;
1198 readBuffer
.numFrames
= framesPerIndexRead
;
1200 if (device
->deviceHandle
.readAudio (readBuffer
))
1205 return readBuffer
.index
;
1210 const Array
<int> AudioCDReader::findIndexesInTrack (const int trackNumber
)
1212 using namespace CDReaderHelpers
;
1213 Array
<int> indexes
;
1215 const int trackStart
= getPositionOfTrackStart (trackNumber
);
1216 const int trackEnd
= getPositionOfTrackStart (trackNumber
+ 1);
1218 bool needToScan
= true;
1220 if (trackEnd
- trackStart
> 20 * 44100)
1222 // check the end of the track for indexes before scanning the whole thing
1224 int pos
= jmax (trackStart
, trackEnd
- 44100 * 5);
1225 bool seenAnIndex
= false;
1227 while (pos
<= trackEnd
- samplesPerFrame
)
1229 const int index
= getIndexAt (pos
);
1233 // lead-out, so skip back a bit if we've not found any indexes yet..
1239 if (pos
< trackStart
)
1253 pos
+= samplesPerFrame
* framesPerIndexRead
;
1260 CDDeviceWrapper
* const device
= static_cast <CDDeviceWrapper
*> (handle
);
1262 int pos
= trackStart
;
1265 while (pos
< trackEnd
- samplesPerFrame
* 10)
1267 const int frameNeeded
= pos
/ samplesPerFrame
;
1269 device
->overlapBuffer
.dataLength
= 0;
1270 device
->overlapBuffer
.startFrame
= 0;
1271 device
->overlapBuffer
.numFrames
= 0;
1272 device
->jitter
= false;
1274 firstFrameInBuffer
= 0;
1276 CDReadBuffer
readBuffer (4);
1277 readBuffer
.wantsIndex
= true;
1280 for (i
= 5; --i
>= 0;)
1282 readBuffer
.startFrame
= frameNeeded
;
1283 readBuffer
.numFrames
= framesPerIndexRead
;
1285 if (device
->deviceHandle
.readAudio (readBuffer
))
1292 if (readBuffer
.index
> last
&& readBuffer
.index
> 1)
1294 last
= readBuffer
.index
;
1298 pos
+= samplesPerFrame
* framesPerIndexRead
;
1301 indexes
.removeValue (trackStart
);
1307 void AudioCDReader::ejectDisk()
1309 using namespace CDReaderHelpers
;
1310 static_cast <CDDeviceWrapper
*> (handle
)->deviceHandle
.openDrawer (true);
1315 #if JUCE_USE_CDBURNER
1317 //==============================================================================
1318 namespace CDBurnerHelpers
1320 IDiscRecorder
* enumCDBurners (StringArray
* list
, int indexToOpen
, IDiscMaster
** master
)
1325 IDiscRecorder
* result
= nullptr;
1327 if (SUCCEEDED (CoCreateInstance (CLSID_MSDiscMasterObj
, 0,
1328 CLSCTX_INPROC_SERVER
| CLSCTX_LOCAL_SERVER
,
1332 if (SUCCEEDED (dm
->Open()))
1334 IEnumDiscRecorders
* drEnum
= nullptr;
1336 if (SUCCEEDED (dm
->EnumDiscRecorders (&drEnum
)))
1338 IDiscRecorder
* dr
= nullptr;
1342 while (drEnum
->Next (1, &dr
, &dummy
) == S_OK
)
1344 if (indexToOpen
== index
)
1349 else if (list
!= nullptr)
1353 if (SUCCEEDED (dr
->GetPath (&path
)))
1354 list
->add ((const WCHAR
*) path
);
1368 if (master
!= nullptr)
1378 //==============================================================================
1379 class AudioCDBurner::Pimpl
: public ComBaseClassHelper
<IDiscMasterProgressEvents
>,
1383 Pimpl (AudioCDBurner
& owner_
, IDiscMaster
* discMaster_
, IDiscRecorder
* discRecorder_
)
1384 : owner (owner_
), discMaster (discMaster_
), discRecorder (discRecorder_
), redbook (0),
1385 listener (0), progress (0), shouldCancel (false)
1387 HRESULT hr
= discMaster
->SetActiveDiscMasterFormat (IID_IRedbookDiscMaster
, (void**) &redbook
);
1388 jassert (SUCCEEDED (hr
));
1389 hr
= discMaster
->SetActiveDiscRecorder (discRecorder
);
1390 //jassert (SUCCEEDED (hr));
1392 lastState
= getDiskState();
1398 void releaseObjects()
1400 discRecorder
->Close();
1401 if (redbook
!= nullptr)
1403 discRecorder
->Release();
1404 discMaster
->Release();
1408 HRESULT __stdcall
QueryCancel (boolean
* pbCancel
)
1410 if (listener
!= nullptr && ! shouldCancel
)
1411 shouldCancel
= listener
->audioCDBurnProgress (progress
);
1413 *pbCancel
= shouldCancel
;
1418 HRESULT __stdcall
NotifyBlockProgress (long nCompleted
, long nTotal
)
1420 progress
= nCompleted
/ (float) nTotal
;
1421 shouldCancel
= listener
!= nullptr && listener
->audioCDBurnProgress (progress
);
1426 HRESULT __stdcall
NotifyPnPActivity (void) { return E_NOTIMPL
; }
1427 HRESULT __stdcall
NotifyAddProgress (long /*nCompletedSteps*/, long /*nTotalSteps*/) { return E_NOTIMPL
; }
1428 HRESULT __stdcall
NotifyTrackProgress (long /*nCurrentTrack*/, long /*nTotalTracks*/) { return E_NOTIMPL
; }
1429 HRESULT __stdcall
NotifyPreparingBurn (long /*nEstimatedSeconds*/) { return E_NOTIMPL
; }
1430 HRESULT __stdcall
NotifyClosingDisc (long /*nEstimatedSeconds*/) { return E_NOTIMPL
; }
1431 HRESULT __stdcall
NotifyBurnComplete (HRESULT
/*status*/) { return E_NOTIMPL
; }
1432 HRESULT __stdcall
NotifyEraseComplete (HRESULT
/*status*/) { return E_NOTIMPL
; }
1434 class ScopedDiscOpener
1437 ScopedDiscOpener (Pimpl
& p
) : pimpl (p
) { pimpl
.discRecorder
->OpenExclusive(); }
1438 ~ScopedDiscOpener() { pimpl
.discRecorder
->Close(); }
1443 JUCE_DECLARE_NON_COPYABLE (ScopedDiscOpener
);
1446 DiskState
getDiskState()
1448 const ScopedDiscOpener
opener (*this);
1451 HRESULT hr
= discRecorder
->QueryMediaType (&type
, &flags
);
1456 if (type
!= 0 && (flags
& MEDIA_WRITABLE
) != 0)
1457 return writableDiskPresent
;
1462 return readOnlyDiskPresent
;
1465 int getIntProperty (const LPOLESTR name
, const int defaultReturn
) const
1467 ComSmartPtr
<IPropertyStorage
> prop
;
1468 if (FAILED (discRecorder
->GetRecorderProperties (prop
.resetAndGetPointerAddress())))
1469 return defaultReturn
;
1472 iPropSpec
.ulKind
= PRSPEC_LPWSTR
;
1473 iPropSpec
.lpwstr
= name
;
1475 PROPVARIANT iPropVariant
;
1476 return FAILED (prop
->ReadMultiple (1, &iPropSpec
, &iPropVariant
))
1477 ? defaultReturn
: (int) iPropVariant
.lVal
;
1480 bool setIntProperty (const LPOLESTR name
, const int value
) const
1482 ComSmartPtr
<IPropertyStorage
> prop
;
1483 if (FAILED (discRecorder
->GetRecorderProperties (prop
.resetAndGetPointerAddress())))
1487 iPropSpec
.ulKind
= PRSPEC_LPWSTR
;
1488 iPropSpec
.lpwstr
= name
;
1490 PROPVARIANT iPropVariant
;
1491 if (FAILED (prop
->ReadMultiple (1, &iPropSpec
, &iPropVariant
)))
1494 iPropVariant
.lVal
= (long) value
;
1495 return SUCCEEDED (prop
->WriteMultiple (1, &iPropSpec
, &iPropVariant
, iPropVariant
.vt
))
1496 && SUCCEEDED (discRecorder
->SetRecorderProperties (prop
));
1499 void timerCallback()
1501 const DiskState state
= getDiskState();
1503 if (state
!= lastState
)
1506 owner
.sendChangeMessage();
1510 AudioCDBurner
& owner
;
1511 DiskState lastState
;
1512 IDiscMaster
* discMaster
;
1513 IDiscRecorder
* discRecorder
;
1514 IRedbookDiscMaster
* redbook
;
1515 AudioCDBurner::BurnProgressListener
* listener
;
1520 //==============================================================================
1521 AudioCDBurner::AudioCDBurner (const int deviceIndex
)
1523 IDiscMaster
* discMaster
= nullptr;
1524 IDiscRecorder
* discRecorder
= CDBurnerHelpers::enumCDBurners (0, deviceIndex
, &discMaster
);
1526 if (discRecorder
!= nullptr)
1527 pimpl
= new Pimpl (*this, discMaster
, discRecorder
);
1530 AudioCDBurner::~AudioCDBurner()
1532 if (pimpl
!= nullptr)
1533 pimpl
.release()->releaseObjects();
1536 StringArray
AudioCDBurner::findAvailableDevices()
1539 CDBurnerHelpers::enumCDBurners (&devs
, -1, 0);
1543 AudioCDBurner
* AudioCDBurner::openDevice (const int deviceIndex
)
1545 ScopedPointer
<AudioCDBurner
> b (new AudioCDBurner (deviceIndex
));
1553 AudioCDBurner::DiskState
AudioCDBurner::getDiskState() const
1555 return pimpl
->getDiskState();
1558 bool AudioCDBurner::isDiskPresent() const
1560 return getDiskState() == writableDiskPresent
;
1563 bool AudioCDBurner::openTray()
1565 const Pimpl::ScopedDiscOpener
opener (*pimpl
);
1566 return SUCCEEDED (pimpl
->discRecorder
->Eject());
1569 AudioCDBurner::DiskState
AudioCDBurner::waitUntilStateChange (int timeOutMilliseconds
)
1571 const int64 timeout
= Time::currentTimeMillis() + timeOutMilliseconds
;
1572 DiskState oldState
= getDiskState();
1573 DiskState newState
= oldState
;
1575 while (newState
== oldState
&& Time::currentTimeMillis() < timeout
)
1577 newState
= getDiskState();
1578 Thread::sleep (jmin (250, (int) (timeout
- Time::currentTimeMillis())));
1584 Array
<int> AudioCDBurner::getAvailableWriteSpeeds() const
1587 const int maxSpeed
= pimpl
->getIntProperty (L
"MaxWriteSpeed", 1);
1588 const int speeds
[] = { 1, 2, 4, 8, 12, 16, 20, 24, 32, 40, 64, 80 };
1590 for (int i
= 0; i
< numElementsInArray (speeds
); ++i
)
1591 if (speeds
[i
] <= maxSpeed
)
1592 results
.add (speeds
[i
]);
1594 results
.addIfNotAlreadyThere (maxSpeed
);
1598 bool AudioCDBurner::setBufferUnderrunProtection (const bool shouldBeEnabled
)
1600 if (pimpl
->getIntProperty (L
"BufferUnderrunFreeCapable", 0) == 0)
1603 pimpl
->setIntProperty (L
"EnableBufferUnderrunFree", shouldBeEnabled
? -1 : 0);
1604 return pimpl
->getIntProperty (L
"EnableBufferUnderrunFree", 0) != 0;
1607 int AudioCDBurner::getNumAvailableAudioBlocks() const
1609 long blocksFree
= 0;
1610 pimpl
->redbook
->GetAvailableAudioTrackBlocks (&blocksFree
);
1614 String
AudioCDBurner::burn (AudioCDBurner::BurnProgressListener
* listener
, bool ejectDiscAfterwards
,
1615 bool performFakeBurnForTesting
, int writeSpeed
)
1617 pimpl
->setIntProperty (L
"WriteSpeed", writeSpeed
> 0 ? writeSpeed
: -1);
1619 pimpl
->listener
= listener
;
1620 pimpl
->progress
= 0;
1621 pimpl
->shouldCancel
= false;
1624 HRESULT hr
= pimpl
->discMaster
->ProgressAdvise ((AudioCDBurner::Pimpl
*) pimpl
, &cookie
);
1626 hr
= pimpl
->discMaster
->RecordDisc (performFakeBurnForTesting
,
1627 ejectDiscAfterwards
);
1632 const char* e
= "Couldn't open or write to the CD device";
1634 if (hr
== IMAPI_E_USERABORT
)
1635 e
= "User cancelled the write operation";
1636 else if (hr
== IMAPI_E_MEDIUM_NOTPRESENT
|| hr
== IMAPI_E_TRACKOPEN
)
1637 e
= "No Disk present";
1642 pimpl
->discMaster
->ProgressUnadvise (cookie
);
1643 pimpl
->listener
= 0;
1648 bool AudioCDBurner::addAudioTrack (AudioSource
* audioSource
, int numSamples
)
1650 if (audioSource
== 0)
1653 ScopedPointer
<AudioSource
> source (audioSource
);
1656 HRESULT hr
= pimpl
->redbook
->GetAudioBlockSize (&bytesPerBlock
);
1658 const int samplesPerBlock
= bytesPerBlock
/ 4;
1661 hr
= pimpl
->redbook
->CreateAudioTrack ((long) numSamples
/ (bytesPerBlock
* 4));
1663 HeapBlock
<byte
> buffer (bytesPerBlock
);
1664 AudioSampleBuffer
sourceBuffer (2, samplesPerBlock
);
1665 int samplesDone
= 0;
1667 source
->prepareToPlay (samplesPerBlock
, 44100.0);
1672 AudioSourceChannelInfo info
;
1673 info
.buffer
= &sourceBuffer
;
1674 info
.numSamples
= samplesPerBlock
;
1675 info
.startSample
= 0;
1676 sourceBuffer
.clear();
1678 source
->getNextAudioBlock (info
);
1681 buffer
.clear (bytesPerBlock
);
1683 typedef AudioData::Pointer
<AudioData::Int16
, AudioData::LittleEndian
,
1684 AudioData::Interleaved
, AudioData::NonConst
> CDSampleFormat
;
1686 typedef AudioData::Pointer
<AudioData::Float32
, AudioData::NativeEndian
,
1687 AudioData::NonInterleaved
, AudioData::Const
> SourceSampleFormat
;
1689 CDSampleFormat
left (buffer
, 2);
1690 left
.convertSamples (SourceSampleFormat (sourceBuffer
.getSampleData (0)), samplesPerBlock
);
1691 CDSampleFormat
right (buffer
+ 2, 2);
1692 right
.convertSamples (SourceSampleFormat (sourceBuffer
.getSampleData (1)), samplesPerBlock
);
1694 hr
= pimpl
->redbook
->AddAudioTrackBlocks (buffer
, bytesPerBlock
);
1699 samplesDone
+= samplesPerBlock
;
1701 if (samplesDone
>= numSamples
)
1705 hr
= pimpl
->redbook
->CloseAudioTrack();
1706 return ok
&& hr
== S_OK
;