Author Topic: How to rip CD tracks as one file?  (Read 10383 times)

3delite

  • Posts: 925
How to rip CD tracks as one file?
« on: 12 Aug '04 - 13:46 »
Any ideas on this?
Also it'd be nice to encode it in real time with BASSEnc.  ::)

Irrational86

  • Posts: 960
Re:How to rip CD tracks as one file?
« Reply #1 on: 12 Aug '04 - 15:52 »
Well...create the CD stream as a decoding channel, then use the BassEnc BASS_Encode_Start function...and do this for every track

3delite

  • Posts: 925
Re:How to rip CD tracks as one file?
« Reply #2 on: 12 Aug '04 - 18:35 »
BASS_Encode_Start() accepts one stream handle. I need to create a user stream and feed all the cd tracks to it. But how do I connect all these (track) streams...?

Ian @ un4seen

  • Administrator
  • Posts: 20768
Re:How to rip CD tracks as one file?
« Reply #3 on: 12 Aug '04 - 21:43 »
You're on the right track... use a custom stream, set the encoder on that, and then decode the CD tracks into it (in the STREAMPROC) one after another.

3delite

  • Posts: 925
Re:How to rip CD tracks as one file?
« Reply #4 on: 19 Aug '04 - 13:14 »
I post my code, couse' I have some problems. It seems that the LAME.exe is executed, it starts a file containing only the LAME header, then program goes into an infinite loop.

Code: [Select]
var
  wholeStream: HSTREAM = 0;
  actStream: HSTREAM = 1;
  TrackReady: Boolean = False;
begin
    // iterate over the cd tracks
    for i := 0 to ListViewTracks.Items.Count - 1 do begin
        StreamAll(ComboBoxDrives.ItemIndex ,i , EditSavePath.Text + 'Album.mp3');
        repeat Application.ProcessMessages;
        until TrackReady;
        TrackReady := False;
    end;
end;

function CallbackStreamAll(Handle: HSTREAM; Buffer: Pointer; Length, User: DWORD): DWORD;
var
  gotData: Integer;
begin
    gotData := BASS_ChannelGetData(actStream, Buffer, Length);
    Result := gotData;
end;

function CallbackCDEnd(OccSync: HSYNC; Channel, Data, User: DWord): Bool; stdcall;
begin
    TrackReady := True;
    actStream := 1;
end;

function TFormRip.StreamAll(DriveNumber, TrackNumber: Integer; SaveFileName: String): Boolean;
var
  Flags: Cardinal;
  lsCmd, EncodeParams: String;
begin
    if wholeStream = 0
        then wholeStream := BASS_StreamCreate(44100, 2, 0, @CallbackStreamAll, 0);

    if actStream = 1 then begin
        actStream := BASS_CD_StreamCreate(DriveNumber, TrackNumber, BASS_CD_FREEOLD OR BASS_STREAM_AUTOFREE OR BASS_STREAM_DECODE);
        if actStream = 0
           then showmessage(Format(STR_ERROR_CDSTREAM, [BASS_ErrorGetCode]))
            else BASS_ChannelSetSync(actStream, BASS_SYNC_END, 0, @CallbackCDEnd, 0);
    end;

    if NOT BASS_Encode_IsActive(wholeStream) then begin
        if SAMPLE_FLOAT
            then Flags := BASS_ENCODE_FP_16BIT
            else Flags := 0;
        Flags := Flags OR BASS_ENCODE_NOHEAD;

        EncodeParams := Presets.Index[ComboBoxPresets.ItemIndex].Params + ' -s 44.1 -t -x -';

        lsCmd := path_Lame + ' '
            + EncodeParams + ' "'
            + SaveFileName
            + '"';

        if (not BASS_Encode_Start(wholeStream, PChar(lsCmd), Flags, nil, 0)) then begin
            MessageDlg(STR_ERROR_STARTINGENCODINGREC, mtError, [mbOk],0);
//            Exit;
        end;
    end;
end;

And also I have a strange feeling that after the "if (not BASS_Encode_Start(wholeStream..." line nothing is executed, or at least I never get the error message. And the callbacks are never executed. :-\

Irrational86

  • Posts: 960
Re:How to rip CD tracks as one file?
« Reply #5 on: 19 Aug '04 - 17:32 »
Ok...first of all, lets make this code a bit more "efficient"

from
Code: [Select]
function CallbackStreamAll(Handle: HSTREAM; Buffer: Pointer; Length, User: DWORD): DWORD;
var
  gotData: Integer;
begin
    gotData := BASS_ChannelGetData(actStream, Buffer, Length);
    Result := gotData;
end;
to
Code: [Select]
function CallbackStreamAll(Handle: HSTREAM; Buffer: Pointer; Length, User: DWORD): DWORD;
begin
    Result := BASS_ChannelGetData(actStream, Buffer, Length);
end;


Second of all, in the doc it says
Quote
BASS_STREAM_DECODE -- Decode the sample data, without outputting it. Use BASS_ChannelGetData to retrieve decoded sample data. BASS_SAMPLE_SOFTWARE/3D/FX/AUTOFREE are all ignored when using this flag, as are the SPEAKER flags.
and you have
Code: [Select]
actStream := BASS_CD_StreamCreate(DriveNumber, TrackNumber, BASS_CD_FREEOLD OR BASS_STREAM_AUTOFREE OR BASS_STREAM_DECODE);
third, you are not playing the custom stream
Code: [Select]
BASS_StreamPlay(wholestream, False, 0);and since you have to be playing it, you can have a volume control on your App to allow the user to hear the CD while encoding, or not to.

BassEnc applies a DSP to the channel to poll the data, so you DO have to be playing the custom stream...and that's all I see...

PS. Since it is a DSP, you might encode faster making the custom stream a Decoding channel, and in a loop call BASS_ChannelGetData continuously until done. But the data polled from BASS_ChannelGetData can be discarded, since the encoder is already writting it to the disk.
« Last Edit: 19 Aug '04 - 17:40 by XMinioNX »

3delite

  • Posts: 925
Re:How to rip CD tracks as one file?
« Reply #6 on: 19 Aug '04 - 18:37 »
Ok...first of all, lets make this code a bit more "efficient"

from
Code: [Select]
function CallbackStreamAll(Handle: HSTREAM; Buffer: Pointer; Length, User: DWORD): DWORD;
var
  gotData: Integer;
begin
    gotData := BASS_ChannelGetData(actStream, Buffer, Length);
    Result := gotData;
end;
to
Code: [Select]
function CallbackStreamAll(Handle: HSTREAM; Buffer: Pointer; Length, User: DWORD): DWORD;
begin
    Result := BASS_ChannelGetData(actStream, Buffer, Length);
end;

It's for debuging, in Delphi you just move the mouse over the variable, and it's contents is displayed. ;)

third, you are not playing the custom stream
Code: [Select]
BASS_StreamPlay(wholestream, False, 0);and since you have to be playing it, you can have a volume control on your App to allow the user to hear the CD while encoding, or not to.

BassEnc applies a DSP to the channel to poll the data, so you DO have to be playing the custom stream...and that's all I see...

PS. Since it is a DSP, you might encode faster making the custom stream a Decoding channel, and in a loop call BASS_ChannelGetData continuously until done. But the data polled from BASS_ChannelGetData can be discarded, since the encoder is already writting it to the disk.

Thanks!!! I knew I was missing something obvious...  :-[
Right, I just need a dummy BASS_ChannelGetData() for the DSP to start processing... nice! :)

...

Exception: Access violation at address 1000752C in module 'bass.dll'. Read of address 0465C000
The problem is in CallbackStreamAll() the Length is 0. Why?  ???
Modified code:

Code: [Select]
var
  wholeStream: HSTREAM = 0;
  actStream: HSTREAM = 1;
  TrackReady: Boolean = False;
  buf: array [0..10000] of DWORD;
begin
    // iterate over the cd tracks
    for i := 0 to ListViewTracks.Items.Count - 1 do begin
        StreamAll(ComboBoxDrives.ItemIndex ,i , EditSavePath.Text + 'Album.mp3');
        repeat
            BASS_ChannelGetData(wholeStream, @buf, 10000);
            Application.ProcessMessages;
        until TrackReady;
        TrackReady := False;
    end;
end;

It locks up on first attempt of CallbackStreamAll() with Length = 0. It should be 10000, right!?  :-\
« Last Edit: 19 Aug '04 - 19:00 by 3delite »

sgdowney

  • Posts: 30
Re:How to rip CD tracks as one file?
« Reply #7 on: 19 Aug '04 - 22:03 »
You don't have to play the stream.  Use a timer as suggested for the BASS_ChannelGetData call.  You can check to see if the channel is still active to determine if the CD track is done.

3delite

  • Posts: 925
Re:How to rip CD tracks as one file?
« Reply #8 on: 20 Aug '04 - 13:55 »
I don't.
Timer would be slower. This way it's more like an OnIdle event, and data is polled when there are no other tasks, so its fast. The problem is, I think, with the StreamAll callback. Why is it requesting 0 bytes in the callback, when I requested 10000 bytes with
Code: [Select]
BASS_ChannelGetData(wholeStream, @buf, 10000);
???

This is my first attempt with custom streams...  :-X

Ian @ un4seen

  • Administrator
  • Posts: 20768
Re:How to rip CD tracks as one file?
« Reply #9 on: 20 Aug '04 - 14:13 »
Have you made the custom stream a decoding channel? It isn't in your code above, but maybe you've changed it since? :)

3delite

  • Posts: 925
Re:How to rip CD tracks as one file?
« Reply #10 on: 20 Aug '04 - 16:53 »
Yeah, yeah... I also tried running it outside the Delphi debugger, but got the same "exception in bass.dll"...
I'll experimtent with it leter...
But is there a chance for the STREAMPROC to request 0 bytes for any reason?
Or/and will the BASS_ChannelGetData() fail when requesting 0 bytes?
Just tought about it, didn't try it yet...  ::)

Irrational86

  • Posts: 960
Re:How to rip CD tracks as one file?
« Reply #11 on: 20 Aug '04 - 17:18 »
BASS_ChannelGetData with length = 0 is the same as the BASS_DATA_AVAILABLE flag. But is the custom stream a decoding channel? Did you add the flag? Show the new code (if you can) to try and figure more out..

3delite

  • Posts: 925
Re:How to rip CD tracks as one file?
« Reply #12 on: 21 Aug '04 - 12:01 »
Ok. So here is once again (current state):

Code: [Select]
var // Global vars
  wholeStream: HSTREAM = 0;
  actStream: HSTREAM = 1;
  TrackReady: Boolean = False;
  buf: array [0..10000] of DWORD;  
begin
    actStream := 1;
    wholeStream := 0;
    // iterate over the cd tracks
    for i := 0 to ListViewTracks.Items.Count - 1 do begin
       StreamAll(ComboBoxDrives.ItemIndex ,i , EditSavePath.Text + 'Album.mp3');
        repeat
            Application.ProcessMessages;
            BASS_ChannelGetData(wholeStream, @buf, 10000);
        until TrackReady;
        TrackReady := False;
    end;
end;

function CallbackStreamAll(Handle: HSTREAM; Buffer: Pointer; Length, User: DWORD): DWORD;
var
  gotData: Integer;
begin
    Result := 0;
    if Length < 1   // well this didn't help after all
        then Exit;   // exiting this function will cause an exception
    gotData := BASS_ChannelGetData(actStream, Buffer, Length);
    Result := gotData;
end;

function CallbackCDEnd(OccSync: HSYNC; Channel, Data, User: DWord): Bool; stdcall;
begin
    TrackReady := True;
    actStream := 1;
end;

function StreamAll(DriveNumber, TrackNumber: Integer; SaveFileName: String): Boolean;
var
  Flags: Cardinal;
  lsCmd, EncodeParams: String;
begin
    if wholeStream = 0
        then wholeStream := BASS_StreamCreate(44100, 2, BASS_STREAM_DECODE, @CallbackStreamAll, 0);

    if actStream = 1 then begin
        actStream := BASS_CD_StreamCreate(DriveNumber, TrackNumber, BASS_CD_FREEOLD OR BASS_STREAM_DECODE);
        if actStream = 0
           then showmessage(Format(STR_ERROR_CDSTREAM, [BASS_ErrorGetCode]))
            else BASS_ChannelSetSync(actStream, BASS_SYNC_END, 0, @CallbackCDEnd, 0);
    end;

    if NOT BASS_Encode_IsActive(wholeStream) then begin
        if SAMPLE_FLOAT
            then Flags := BASS_ENCODE_FP_16BIT
            else Flags := 0;
        Flags := Flags OR BASS_ENCODE_NOHEAD;

        EncodeParams := Presets.Index[ComboBoxPresets.ItemIndex].Params + ' -s 44.1 -t -x -';

        lsCmd := path_Lame + ' '
            + EncodeParams + ' "'
            + SaveFileName
            + '"';

        if (not BASS_Encode_Start(wholeStream, PChar(lsCmd), Flags, nil, 0)) then begin
            MessageDlg(STR_ERROR_STARTINGENCODINGREC, mtError, [mbOk],0);
//            Exit;
        end;
    end;
end;

Ian @ un4seen

  • Administrator
  • Posts: 20768
Re:How to rip CD tracks as one file?
« Reply #13 on: 22 Aug '04 - 17:33 »
Code: [Select]
function CallbackStreamAll(Handle: HSTREAM; Buffer: Pointer; Length, User: DWORD): DWORD;

Don't know if it'll make the difference, but there should probably be a "stdcall;" on the end of that.

3delite

  • Posts: 925
Re:How to rip CD tracks as one file?
« Reply #14 on: 22 Aug '04 - 18:43 »
Don't know if it'll make the difference, but there should probably be a "stdcall;" on the end of that.

Nicetry! Stupid mistake.  ::)
Definetly it's needed for the proper register assignment, that's way Length was 0.
I thoght this will finnaly fix it, but it didn't!!!???  :o :'(
Exception: Access violation at address 77F5D84E in module 'ntdll.dll'. Write of address 241E008B
It's still when exiting the function CallbackStreamAll().
At least now the first block of the CD stream is transferred succesfully.

Ian @ un4seen

  • Administrator
  • Posts: 20768
Re:How to rip CD tracks as one file?
« Reply #15 on: 23 Aug '04 - 15:17 »
You say "the first block of the CD stream is transferred succesfully", does that mean data is written to the encoded output file? But you also say it crashes when exiting the CallbackStreamAll function, which kind of contradicts that :)

Anyway...

Code: [Select]
function CallbackStreamAll(Handle: HSTREAM; Buffer: Pointer; Length, User: DWORD): DWORD;
var
  gotData: Integer;
begin
    Result := 0;
    if Length < 1   // well this didn't help after all
        then Exit;   // exiting this function will cause an exception
    gotData := BASS_ChannelGetData(actStream, Buffer, Length);
    Result := gotData;
end;

If an error occurs (eg. "actStream" is an invalid handle), BASS_ChannelGetData will return -1, which is a very big number in unsigned (DWORD) terms, so you should probably check for that...

Code: [Select]
   gotData := BASS_ChannelGetData(actStream, Buffer, Length);
    if gotData<0 then gotData := 0;
    Result := gotData;

3delite

  • Posts: 925
gotData is 10000. And yes the MP3 file starts growing. This is really wierd. :)
If I am running the exe separately, i got exception in ntdll.dll, and yes the output file has some frames (around 10KB in size), but when I run it inside Delphi and after the crash I keep on pressing the Play button (continue executing) the file growed all the way to 60KB!!!
Now what??  :D

About the contradicton: The execution point cursor never returns in my code after leaving the callback function.

Ian @ un4seen

  • Administrator
  • Posts: 20768
Re:How to rip CD tracks as one file?
« Reply #17 on: 24 Aug '04 - 13:54 »
After crashing it (outside the debugger), upload the drwtsn32.log entry to have a look at...

   ftp.un4seen.com/incoming/

3delite

  • Posts: 925
Re:How to rip CD tracks as one file?
« Reply #18 on: 25 Aug '04 - 13:19 »
There are some problems with that. There is no drwatson32.log. The program is not crashing, i;s only my exception handler, that'll report the hang. I considered that and with the following I almost managed to succesfully rip the first track (around 70%). I'll see what I can do...

Code: [Select]
begin
    actStream := 1;
    wholeStream := 0;
    // iterate over the cd tracks
    for i := 0 to ListViewTracks.Items.Count - 1 do begin
        StreamAll(ComboBoxDrives.ItemIndex ,i , EditSavePath.Text + 'Album.mp3');
        repeat
            Application.ProcessMessages;
            try
                BASS_ChannelGetData(wholeStream, @buf, 10000);
            except
                //* nil
            end;
        until TrackReady;
        TrackReady := False;
    end;
end;

3delite

  • Posts: 925
Re:How to rip CD tracks as one file? Made simple
« Reply #19 on: 25 Aug '04 - 16:11 »
Oh right, 32bit sample data was on.  8)
With that stdcall, they both coincided at the same spot, that is after leaving CallbackStreamAll(). :D

So finally:

Code: [Select]
var // Global vars
  wholeStream: HSTREAM = 0;
  actStream: HSTREAM = 1;
  TrackReady: Boolean = False;
  // if 16bit sample data
  buf: array [0..10000] of DWORD;
  // if 32bit sample data
  // buf: array [0..10000] of Single;
begin
    actStream := 1;
    wholeStream := 0;
    // iterate over the cd tracks
    for i := 0 to ListViewTracks.Items.Count - 1 do begin
       StreamAll(ComboBoxDrives.ItemIndex ,i , EditSavePath.Text + 'Album.mp3');
        repeat
            Application.ProcessMessages;
            BASS_ChannelGetData(wholeStream, @buf, 10000);
        until TrackReady;
        TrackReady := False;
    end;
end;

function CallbackStreamAll(Handle: HSTREAM; Buffer: Pointer; Length, User: DWORD): DWORD;
begin
    Result :=  BASS_ChannelGetData(actStream, Buffer, Length);
end;

function CallbackCDEnd(OccSync: HSYNC; Channel, Data, User: DWord): Bool; stdcall;
begin
    TrackReady := True;
    actStream := 1;
end;

function StreamAll(DriveNumber, TrackNumber: Integer; SaveFileName: String): Boolean;
var
  Flags: Cardinal;
  lsCmd, EncodeParams: String;
begin
    if wholeStream = 0
        then wholeStream := BASS_StreamCreate(44100, 2, BASS_STREAM_DECODE, @CallbackStreamAll, 0);

    if actStream = 1 then begin
        actStream := BASS_CD_StreamCreate(DriveNumber, TrackNumber, BASS_CD_FREEOLD OR BASS_STREAM_DECODE);
        if actStream = 0
           then showmessage(Format(STR_ERROR_CDSTREAM, [BASS_ErrorGetCode]))
            else BASS_ChannelSetSync(actStream, BASS_SYNC_END, 0, @CallbackCDEnd, 0);
    end;

    if NOT BASS_Encode_IsActive(wholeStream) then begin
        if SAMPLE_FLOAT
            then Flags := BASS_ENCODE_FP_16BIT
            else Flags := 0;
        Flags := Flags OR BASS_ENCODE_NOHEAD;

        EncodeParams := Presets.Index[ComboBoxPresets.ItemIndex].Params + ' -s 44.1 -t -x -';

        lsCmd := path_Lame + ' '
            + EncodeParams + ' "'
            + SaveFileName
            + '"';

        if (not BASS_Encode_Start(wholeStream, PChar(lsCmd), Flags, nil, 0)) then begin
            MessageDlg(STR_ERROR_STARTINGENCODINGREC, mtError, [mbOk],0);
            Exit;
        end;
    end;
end;

Thanx for the help! :)