More than 10 years ago I started XEdit project which is binary editor for files and disks contents. XEdit is available there. I am still using it sometime, however I do not support the project since 2011 mainly because this MFC application was written on Visual C++ 6 and I have no time to do migration to MS VS 20xx. The edit has a lot of useful tools such as search for binary sequences in files and disk sectors (to get access to disk level XEdit should be started as administrator).
XEdit also has audio CD ripping tool which I want to show how it works from inside. There are a lot of free and commercial software which does the same and ripping by it self is very simple and consists of 3 following steps:
- Find binary sequence which corresponds to appropriate sound track (song)
- Read this binary sequence
- Save them to sound file
The source code from XEdit which is doing all 3 steps may be downloaded using link below:
Ripping source code
In the first step you need to read CD TOC (table of content) which shows location of all sound tracks on the CD, for example this one:

The raw view TOC from this CD is:

Then the raw TOC data are casted to _CDROM_TOC structure:
|
typedef struct _TRACK_DATA { UCHAR nbsp; Reserved; UCHAR nbsp; Control : 4; UCHAR nbsp; Adr : 4; UCHAR nbsp; TrackNumber; UCHAR nbsp; Reserved1; UCHAR nbsp; Address[4]; } TRACK_DATA, *PTRACK_DATA; typedef struct _CDROM_TOC { |
The first 2 bytes is length of tracks data, then one byte is the first track number and next one is the last track number. The total number of tracks is 13 the same as song number of this CD.
Now the code which reads CD TOC:
|
void RippingCDDlg::OnRead() { CString csBuffer; DWORD dwLength; m_nResult = -1; CDROM_DISK_DATA cdd; DWORD dwData; m_pExeditView->m_nDiskType = -1; //read it again, may floppy was reamoved or changed if(GetDriveGeometry((LPCTSTR)m_pExeditView->m_csDefaultCD,&m_dg)) { UpdateDialog(); csBuffer = "\\\\.\\"; csBuffer += m_pExeditView->m_csDefaultCD; //read from disc m_hDrive = CreateFile((LPCTSTR)csBuffer, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, /*FILE_FLAG_RANDOM_ACCESS,*/ FILE_FLAG_NO_BUFFERING, NULL); if(m_hDrive != INVALID_HANDLE_VALUE) { if(DeviceIoControl(m_hDrive, IOCTL_CDROM_DISK_TYPE, NULL, 0, &cdd, sizeof(cdd), &dwData, 0)) { switch (cdd.DiskData & 0x03) { case CDROM_DISK_DATA_TRACK: m_pExeditView->m_nDiskType = CDROM_DISK_DATA_TRACK; csBuffer = "Data"; break; case CDROM_DISK_AUDIO_TRACK: m_pExeditView->m_nDiskType = CDROM_DISK_AUDIO_TRACK; csBuffer = "Audio"; break; case CDROM_DISK_DATA_TRACK|CDROM_DISK_AUDIO_TRACK: m_pExeditView->m_nDiskType = CDROM_DISK_DATA_TRACK|CDROM_DISK_AUDIO_TRACK; csBuffer = "Audio + Data"; default: m_pExeditView->m_nDiskType = 0; csBuffer = "Unknow type"; break; } } if (DeviceIoControl(m_hDrive, IOCTL_CDROM_READ_TOC, NULL, 0, &m_pExeditView->m_toc, sizeof(m_pExeditView->m_toc), &dwData, 0)) { if(dwData!=0) { m_nResult = IDOK; m_pExeditView->m_int64Length = dwData; if(*m_ppbyFileBuffer) { // delete[] m_pbyFileBuffer; free(*m_ppbyFileBuffer); *m_ppbyFileBuffer = NULL; } *m_ppbyFileBuffer = new BYTE[dwData]; memcpy(*m_ppbyFileBuffer,(const void*)&m_pExeditView->m_toc,dwData); int nAttribLen = dwData/4; if(dwData % 4) nAttribLen++; if(*m_ppbyFileBufferAttribute) { free(*m_ppbyFileBufferAttribute); *m_ppbyFileBufferAttribute = NULL; } *m_ppbyFileBufferAttribute = (BYTE*)malloc(nAttribLen); if(*m_ppbyFileBufferAttribute) memset(*m_ppbyFileBufferAttribute,0,nAttribLen); m_pExeditView->m_csFileName.Format("Raw TOC of %s (%s)", (LPCTSTR)m_pExeditView->m_csDefaultCD, (LPCTSTR)csBuffer); } else { m_nResult = -1; } } CloseHandle(m_hDrive); } } if(m_nResult != IDOK) { csBuffer.Format("Cannot read Table of Contents of %s device or no such device", m_pExeditView->m_csDefaultCD); AfxMessageBox((LPCTSTR)csBuffer); m_nResult = -1; } EndDialog(m_nResult); } |
Raw data may be converted to user friendly view, see TOCDialog::OnInitDialog function using “Ripping source code” link:

Now reading sound track data per song:
|
int TOCDialog::ReadCDTrack(BYTE ** ppBuffer, BOOL bFineReading) { DWORD dwStartFrame; DWORD dwNumOfFrame; DWORD dwBytesRdWr; RAW_READ_INFO rawRead; int nIndex = 0; int nPos = 0; BOOL bRet; DISK_GEOMETRY dg; BOOL bDataCD; int int64Len = 0; CDROM_DISK_DATA cdd; DWORD dwData; DWORD dwRetVal = (DWORD)-1; LARGE_INTEGER n64Pos; DWORD dwLength; CString csBuffer; CListCtrl * pListCtrl = (CListCtrl*)GetDlgItem(IDC_LIST_TOC); HCURSOR hWaitCursor = LoadCursor( NULL, IDC_WAIT); pProgressCtrl->SetRange(0, dwNumOfFrame); |
When track data have been read they are save to wav file as is after WaveFileHeader info:
|
int nFileHandler = _open((LPCTSTR)csFileName, _O_WRONLY | _O_BINARY | _O_TRUNC); if( nFileHandler != -1) { memcpy(WaveFileHeader.szRIFF,”RIFF”,4); memcpy(WaveFileHeader.szWAVE, “WAVE”, 4); memcpy(WaveFileHeader.szfmt,”fmt “,4); WaveFileHeader.dwLenOfChunk = 0x10; WaveFileHeader.wCode = 1; WaveFileHeader.wChannelNumbers = 0x02; //Stereo WaveFileHeader.dwSampleRate = 44100; WaveFileHeader.wBytesPerSample = 4; WaveFileHeader.dwBytesPerSecond = WaveFileHeader.dwSampleRate* WaveFileHeader.wBytesPerSample; WaveFileHeader.wBitsPerSample = 0x10; WaveFileHeader.dwFileLengthMinus8 = sizeof(WaveFileHeader) + int64Len – 8; WaveFileHeader.dwLengthOfSoundDataToFollow = int64Len; _write(nFileHandler, (void*)&WaveFileHeader, sizeof(WaveFileHeader)); _write(nFileHandler, (void*)pbyBuffer, int64Len); _close(nFileHandler); delete[] pbyBuffer; } else { csBuffer.Format(“File %s cannot be saved!”,(LPCTSTR)csFileName); AfxMessageBox(csBuffer); } |