Author Topic: BASS and Lazarus  (Read 402 times)

stephanos

  • Posts: 9
BASS and Lazarus
« on: 23 Nov '21 - 13:37 »
Greetings

I am writing a playlist programme for my mp3 player using Lazarus on Windows 10.  I want to access some file properties so as to calculate the playtime of each file listed in the playlist file. That way I know how much play time I have and if I need more for the journey.  I can use FileSize() and/or FindFirst() to get the files size.  I can use id3 tag to get more information found in the last 128 bytes of the file.  But this does not include bitrate or Length.

Does BASS as a plug-in allow me to code for this information?

Does BASS as a plug-in work in Lazarus?

If so are there any instructions?  I have unpacked it into a folder “bass24” but what to do with it?

Thanks and wait to hear

ManlioMZ

  • Posts: 21
Re: BASS and Lazarus
« Reply #1 on: 23 Nov '21 - 13:48 »
The following example was tested with Lazarus + Windows.

1. Open the file with a stream.
2. Get the stream size (in bytes).
3. Convert the number of bytes to seconds.

Code: [Select]
  function GetMp3Len(fn: string): integer;
  var
    H: HSTREAM;
    Info: BASS_CHANNELINFO;
    L_Bytes: QWORD; // length in bytes
    L_Seconds: double; // length in seconds
  begin
    result := 0;
    H := BASS_StreamCreateFile(false, pchar(Utf8Encode(fn)), 0, 0, BASS_STREAM_DECODE);
    if H=0 then exit; // Something's wrong, could not open the file
    L_Bytes := BASS_ChannelGetLength(H, BASS_POS_BYTE); // length in bytes
    L_Seconds := BASS_ChannelBytes2Seconds(H, L_Bytes); // convert bytes to seconds
    result := trunc(L_Seconds * 1000); // return the result as milliseconds
    BASS_StreamFree(H); // close the stream and free the resources
  end;

Note that the above requires opening the file and decoding it first, so the speed depends on the length of the file.
« Last Edit: 23 Nov '21 - 20:34 by ManlioMZ »

stephanos

  • Posts: 9
Re: BASS and Lazarus
« Reply #2 on: 23 Nov '21 - 18:22 »
Dear ManlioMZ

Thanks for the info and the offer which I accept.  In the final programme I might make the calculation of the play time a separate button so as to separate it from the other validation that takes place on the list of mp3 files selected.

Wait to hear


ManlioMZ

  • Posts: 21
Re: BASS and Lazarus
« Reply #3 on: 23 Nov '21 - 20:39 »
I added the code above. Note that:

1. Filenames and UTF encoding may work slightly differently on different systems. Do some tests with weird filenames to make sure.

2. You can use a similar approach to read the data from a TMemoryStream instead of a file, like this (Where MS is a TMemoryStream):

  H := BASS_StreamCreateFile(True, MS.Memory, 0, 0, BASS_STREAM_DECODE);

3. The tag BASS_STREAM_DECODE means you don't want to play the file, only measure it. This, I presume, saves time and spares some system resources.

stephanos

  • Posts: 9
Re: BASS and Lazarus
« Reply #4 on: 24 Nov '21 - 10:55 »
Dear  ManlioMZ

Thanks for that code.  I have many questions
1) what do I put in uses?
2) how do I integrate BASS into Lazarus?  I have unpacked the zip but I do not know where to place the folder "bass24" 3) how do I tell Lazarus that it now has this facility

Thanks and wait to hear


morknot

  • Posts: 21
Re: BASS and Lazarus
« Reply #5 on: 27 Nov '21 - 14:09 »
Copy the bass.pas file from the Bass24/Delphi folder to your application's folder. Copy bass.dll to the same folder.

You can use Lazarus's built-in Delphi unit converter , but it may not be necessary.

interface

uses

 Classes, Forms, bass;


That's it.


stephanos

  • Posts: 9
Re: BASS and Lazarus
« Reply #6 on: 29 Nov '21 - 12:11 »
Dear morknot and all

Thanks, I have followed this so far.  One piece of information I must get is the bitrate.  Most of my files are ripped CDs and I did them all at 128kbps.  However, there are some large files, audio recordings, of different radio programmes.  These are each recorded at various bit rates (as opposed to recorded with a changing bitrate during the recording).

So how do I amend this code:
L_Bytes := BASS_ChannelGetLength(H, BASS_POS_BYTE); // length in bytes
so as to get the bitrate?
Something like:
L_Bytes := BASS_ChannelGetBitRateh(H, BASS_POS_BYTE); // bit rate
I am happy to study documentation as I am sure I will have need of copying more file data in the future.

Any further help welcome

Stephanos

stephanos

  • Posts: 9
Re: BASS and Lazarus
« Reply #7 on: 29 Nov '21 - 12:17 »
Dear All

I should add that inside the folder bass24 is a file: bass.chm, which when I double click opens in the intended application.  However, only the contents is visible.  When I click any thing in the contents nothing is loaded in the main page.  And no error messages either.

stephanos

  • Posts: 9
Re: BASS and Lazarus
« Reply #8 on: 29 Nov '21 - 13:42 »
Dear All

Here is an update:
1) I have found documentation online:
http://www.un4seen.com/doc/#bass/BASS_StreamCreateFile.html
2) the code supplied earlier gets me the length in seconds and takes into account the individual files bitrate.
3) That means I do not extract an individuals files bitrate - can anyone confirm 2 and 3

Assuming this is true then I am very close to completing the task of getting the total play time of selected files.  Currently I have coded for every file having 128kbps.

My current problem is that I keep getting error code -1 when using
Code: [Select]
H := BASS_StreamCreateFile(false, pchar(Utf8Encode('BAILE MINHA MADEIRA.mp3')), 0, 0, BASS_STREAM_DECODE);and
Code: [Select]
H := BASS_StreamCreateFile(false, pchar(Utf8Encode('BAILE MINHA MADEIRA.mp3')), 0, 0, BASS_STREAM_PRESCAN);and
Code: [Select]
H := BASS_StreamCreateFile(true, pchar(Utf8Encode('BAILE MINHA MADEIRA.mp3')), 0, 0, BASS_STREAM_DECODE);and
Code: [Select]
H := BASS_StreamCreateFile(true, pchar(Utf8Encode('BAILE MINHA MADEIRA.mp3')), 0, 0, BASS_STREAM_PRESCAN);
Both bass.pas and bass.dll and BAILE MINHA MADEIRA.mp3 are in the project folder

Wait to hear

 

Chris

  • Posts: 1993
Re: BASS and Lazarus
« Reply #9 on: 29 Nov '21 - 14:18 »
Hi
you can try something like this

Code: [Select]
function TimeToString(Time: int64): string;
begin
   if Time >= 3600000 then
      Result := Format('%d:%2.2d:%2.2d.%3.3d',[Time div 3600000,
                                              (Time mod 3600000) div 60000,
                                              (Time mod 60000) div 1000,
                                               Time mod 1000])
   else
      Result := Format('%d:%2.2d.%3.3d',[Time div 60000,
                                        (Time mod 60000) div 1000,
                                         Time mod 1000]);
end; 



Code: [Select]
function GetChannelLength(Chan:HStream) : string;
var
  FloatLen: single;
begin
 Floatlen := BASS_ChannelBytes2Seconds(Chan, BASS_ChannelGetLength(Chan,BASS_POS_BYTE));
 Result := TimeToString(round(FloatLen * 1000));// * 1000 we want to get the Pos in Milliseconds
end; 

Code: [Select]
if H <> 0 then
   Bass_StreamFree(H);
H := BASS_StreamCreateFile(false, pchar(Utf8Encode('BAILE MINHA MADEIRA.mp3')), 0, 0, BASS_STREAM_DECODE or BASS_STREAM_PRESCAN );
if (H <> 0) then
begin
    Listbox1.Items.Add('BAILE MINHA MADEIRA.mp3'+'----'+ GetChannelLength(Chan))
end;
« Last Edit: 29 Nov '21 - 15:10 by Chris »

Chris

  • Posts: 1993
Re: BASS and Lazarus
« Reply #10 on: 29 Nov '21 - 15:09 »
So here is an absolute simple Lazarus (x86) Demo that will show it.

stephanos

  • Posts: 9
Re: BASS and Lazarus
« Reply #11 on: 29 Nov '21 - 19:42 »
Dear All

Thanks for further feed back.  Some of it made sense.  Let me mention that I am not interested in milliseconds.  If I ever get the length in seconds I will calculate the hours (/ 3600), mins (/ 60) and seconds from that.

Are items 2 and 3 in my earlier post correct.  Some feedback please.

However, my problem does not appear to be related to formatting a float (which is new to me and I did not understand all the %d etc).

Meanwhile I looked here at error codes: http://www.un4seen.com/doc/#bass/BASS_ErrorGetCode.html
There are 2 things to note.
A) int BASS_ErrorGetCode(); - this means that on success or failure an integer is returned and seemed to me a better way to test than if ( H = 0) or if ( H <> 0).  I will say more in a minute
B) that within a list of error codes is a non error code.  Silly me, I started to think that it returned a string: ' BASS_OK' when it worked.

Please study the code below.  At first I tried 
Code: [Select]
if ( H = 0) then and always got a true and then
Code: [Select]
writeln(L_Seconds); always output: 'True        -1.00000000000 E00' or something very similar

I got suspicious and changed to the code below:
Code: [Select]
program Project1; //
{$mode objfpc} {$H+}
uses
  bass, FileCtrl,   StdCtrls, Graphics, Controls, Classes, SysUtils, LResources, Forms, Dialogs,
  Interfaces;
var
           EC : integer;   H : HSTREAM;   Info : BASS_CHANNELINFO;
      L_Bytes : QWORD;  // length in bytes
    L_Seconds : double; // length in seconds

begin
     // File size is 2469 KB = 2,528,256 Bytes = 20,226,048 bits
     //H := BASS_StreamCreateFile(FALSE, pchar(Utf8Encode('BAILE MINHA MADEIRA.mp3')), 0, 0, BASS_STREAM_DECODE);
     //H := BASS_StreamCreateFile(FALSE, pchar('BAILE MINHA MADEIRA.mp3'), 0, 0, BASS_STREAM_DECODE);
     //H := BASS_StreamCreateFile(FALSE, pchar(Utf8Encode('BAILE MINHA MADEIRA.mp3')), 0, 0, BASS_STREAM_PRESCAN);
     //H := BASS_StreamCreateFile(TRUE, pchar(Utf8Encode('BAILE MINHA MADEIRA.mp3')), 0, 0, BASS_STREAM_DECODE);
     H := BASS_StreamCreateFile(TRUE, pchar(Utf8Encode('BAILE MINHA MADEIRA.mp3')), 0, 0, BASS_STREAM_PRESCAN);
//
     EC := BASS_ErrorGetCode();  writeln(EC);writeln();writeln(); // 8 
     if (EC = 0) then //
     begin
            L_Bytes := BASS_ChannelGetLength(H, BASS_POS_BYTE); // length in bytes
          L_Seconds := BASS_ChannelBytes2Seconds(H, L_Bytes);   // convert bytes to seconds
          write('True   ');  writeln(L_Seconds); //
          EC := BASS_ErrorGetCode(); writeln(EC);//
     end
     else
     begin
           writeln(EC);    // 8
     end;
     BASS_StreamFree(H); // close the stream and free the resources
     readln();
end.   

This test is failing with whichever
Code: [Select]
H := BASS_St.... I use and always with error code 8. BASS_ERROR_INIT.

So for the moment and until I get the file read OK, I need to not worry about formatting a long float, as I think it is stray data caused by the
Code: [Select]
L_Bytes := BASS_ChannelGetLength(H, BASS_POS_BYTE);not being populated with correct data

Any further help appreciated

Chris

  • Posts: 1993
Re: BASS and Lazarus
« Reply #12 on: 29 Nov '21 - 21:12 »
You are here mixing things so
without ms you can change the code to this (tested with Lazarus)

Code: [Select]
function GetChannelLength(Chan:HStream) : string;
var
  FloatLen: double;
begin
 Floatlen := BASS_ChannelBytes2Seconds(Chan, BASS_ChannelGetLength(Chan,BASS_POS_BYTE));
 Result := FormatDateTime('hh:nn:ss', Floatlen / SecsPerDay; // will return a string e.g 00:05:35
end;


about your Init error at first
Code: [Select]
procedure TForm1.FormCreate(Sender: TObject);
begin
  Bass_Init(-1,44100,0,handle,nil);
end;

Code: [Select]
procedure TForm1.FormDestroy(Sender: TObject);
begin
  Bass_Free();
end; 

The Return of Bass_StreamCreateFile is a DWORD will mean
Return 0 = Error
Return > 0 successfully Audiostream

about the Flags in the Bass_StreamCreateFile Call
BASS_STREAM_DECODE (the stream is only decoded, if you want to play the stream remove the flag)
BASS_STREAM_PRESCAN (Pre-scan the file for accurate seek points and length reading in MP3/MP2/MP1 files and chained OGG files )

so this way makes more sense
 
Code: [Select]
if open1.Execute then
  begin
    StringGrid1.RowCount:= open1.files.count+1; // +1 wo want to add after the Fixed Header
  for i := 0 to open1.Files.Count -1 do
  begin
    Bass_StreamFree(Chan);
    Chan := BASS_StreamCreateFile(false, PChar(open1.Files[i]), 0, 0, BASS_STREAM_DECODE or BASS_STREAM_PRESCAN );
    if Chan <> 0 then // we have a valid Audiostream
    begin
      Stringgrid1.Cells[0,i+1]:= ExtractFileName(open1.Files[i]);
      StringGrid1.Cells[1,i+1] := GetChannelLength(chan);
    end else
     begin
        // something going wrong lets catch the Error with Bass_ErrorGetCode
     end;
   end;
 end; 
       
« Last Edit: 30 Nov '21 - 08:54 by Chris »

stephanos

  • Posts: 9
Re: BASS and Lazarus
« Reply #13 on: 30 Nov '21 - 11:39 »
Dear Chris

Your way makes no sense to me as I never get an explanation as to what the code is doing and I do not understand the code.  Plus you seem to be intent that I go into a loop.  I have no need of a loop.  So far I am trying to access additional information in a specific file.  I made a guess when I read this
 
Code: [Select]
"H := BASS_StreamCreateFile(false, pchar(Utf8Encode(fn)), 0, 0, BASS_STREAM_DECODE);"and concluded that “fn” meant file name. 

I am not expecting everything to be on a plate.  I like working things out and learning new code but your contributions are pitched too high, sorry.

You are not obliged to be a teacher.  Teaching is a different skill to instructing.  If either is pitched too high it will fail and has done on this occasion.  I have been unable to gain an understanding.  I have attempted to gain understanding and asked what the code does, see question 2 and 3 and never received an explanation.  Can you see how if someone had answered the questions I would gain understanding so improving my chances of getting the code right?  It would also mean that my problem is around the if statement.

Regarding "Return 0 = Error".  This appears not to be true because zero is an error return code, found here:
Code: [Select]
https://www.un4seen.com/doc/#bass/BASS_ErrorGetCode.htmland states that zero means BASS_OK.  Have I got something wrong?  Does a return of zero mean the code worked or is there something I do not know about error codes?  I have made this point in my earlier post.  If I am wrong simply saying:
“You are here mixing things so
without ms you can change the code to this (tested with Lazarus)”
does not help

Also, what does “ms” mean?

Here is an example of how I find you examples too hard to understand:
function GetChannelLength(Chan:HStream) : string;
var
  FloatLen: double;
begin
 Floatlen := BASS_ChannelBytes2Seconds(Chan, BASS_ChannelGetLength(Chan,BASS_POS_BYTE));
 Result := FormatDateTime('hh:nn:ss', Floatlen / SecsPerDay; // will return a string e.g 00:05:35
end;

How do I link this long bit of code to the file I am interested in?
Which data type is result?

Please recognise that I know a lot less than I need to in order to achieve this exercise.  Please recognise that I have leant little.  If you can please amend your level of explanation so that you can feel up my knowledge gaps.  I do not know what I do not know so I cannot tell you what I do not know.

So with all that in mind let me try these questions. 
Does this code:
Code: [Select]
H := BASS_StreamCreateFile(false, pchar(Utf8Encode('BAILE MINHA MADEIRA.mp3')), 0, 0, BASS_STREAM_DECODE);
L_Bytes := BASS_ChannelGetLength(H, BASS_POS_BYTE); // length in bytes
L_Seconds := BASS_ChannelBytes2Seconds(H, L_Bytes);       // convert bytes to seconds
create a link to the specified file.  Allow data to be read from the file.  Copy to a variable the size of the file in Bytes.  Use the Bytes to calculate the play time of the file in seconds.  And do that calculation based on the bit rate internally, without me having to code to find the bit rate?

Does the success of this code
Code: [Select]
H := BASS_StreamCreateFile(false, pchar(Utf8Encode('BAILE MINHA MADEIRA.mp3')), 0, 0, BASS_STREAM_DECODE);lead to a value of zero in H, which can then be tested in an if statement?

Thanks and wait to hear

Chris

  • Posts: 1993
Re: BASS and Lazarus
« Reply #14 on: 30 Nov '21 - 15:01 »
Hi

so something like this
Code: [Select]
var
FBitrate : single;

H := BASS_StreamCreateFile(false, pchar(Utf8Encode('BAILE MINHA MADEIRA.mp3')), 0, 0, BASS_STREAM_DECODE or BASS_STREAM_PRESCAN);
if H <> 0 then  // H > 0 = stream is valid
begin
    L_Bytes := BASS_ChannelGetLength(H, BASS_POS_BYTE); // length in bytes
    L_Seconds := BASS_ChannelBytes2Seconds(H, L_Bytes);       // convert bytes to seconds
    BASS_ChannelGetAttribute(H,BASS_ATTRIB_BITRATE,FBitrate); // the bitrate is stored in the Variable FBitrate
end;

About the ErrorCode  0  will mean Bass_Ok  all is OK

Ian @ un4seen

  • Administrator
  • Posts: 24047
Re: BASS and Lazarus
« Reply #15 on: 1 Dec '21 - 12:37 »
Does the success of this code
Code: [Select]
H := BASS_StreamCreateFile(false, pchar(Utf8Encode('BAILE MINHA MADEIRA.mp3')), 0, 0, BASS_STREAM_DECODE);lead to a value of zero in H, which can then be tested in an if statement?

No, 0 indicates that the call failed. You can use BASS_ErrorGetCode to find out why it failed. If the error code is still BASS_ERROR_INIT (as you wrote earlier in the thread) then that means BASS_Init has not been called successfully, which it must be before you can create any streams. If you won't be playing anything then you can use the "No Sound" device in the BASS_Init call:

Code: [Select]
BASS_Init(0, 44100, 0, 0, 0); // initialize "No Sound" device

Include that in your initialization code. If the BASS_StreamCreateFile call still fails then please post the new error code.

stephanos

  • Posts: 9
Re: BASS and Lazarus
« Reply #16 on: 1 Dec '21 - 21:30 »
Dear Ian

I am very grateful for this feedback and for clarifying that I mixed up my understanding of error codes and H = zero.

I did the research on the unforseen website about BASS_Init().  Your suggested code did not compile but via searches for BASS_Init() and Lazarus, I found the solution.
Code: [Select]
BASS_Init(0, 44100, 0, H, NIL); 
Here is the complete code
Code: [Select]
program Project1; //
{$mode objfpc} {$H+}
uses
  bass, FileCtrl,   StdCtrls, Graphics, Controls, Classes, SysUtils, LResources, Forms, Dialogs, Interfaces;
var
           EC : integer;   H : HSTREAM;   Info : BASS_CHANNELINFO; FloatLen : double;
      L_Bits, L_Bytes : QWORD;  // length in bytes
    L_Seconds : double; // length in seconds
    FBitrate : single;

begin
     // Known file info = File size is 2469 KB = 2,528,256 Bytes = 20,226,048 bits, Bitrate = 128kbps, Length = 2 mins 37 sec
     BASS_Init(0, 44100, 0, H, NIL);  // the missing bit of code
     H := BASS_StreamCreateFile(FALSE, pchar(Utf8Encode('BAILE MINHA MADEIRA.mp3')), 0, 0, BASS_STREAM_DECODE);
     EC := BASS_ErrorGetCode();  write('Error Code = ', EC); // 0 meaning BASS_OK

     if (H <> 0) then // when H is equal to zero the stream is not opened
     begin
          L_Bytes := BASS_ChannelGetLength(H, BASS_POS_BYTE); // length in bytes
          writeln(L_Bytes); // 27908540 bytes
          L_Seconds := BASS_ChannelBytes2Seconds(H, L_Bytes); // convert bytes to seconds
          writeln('True, number of seconds = ', Trunc(L_Seconds));  //  157 seconds = 2 mins, 37 seconds, 1.5821167800453514E+002
          BASS_ChannelGetAttribute(H,BASS_ATTRIB_BITRATE, FBitrate);  //
          //writeln('The bit rate is = ', (Trunc(FBitrate));        //  127, Trunc((1.277159348E+02))
          writeln('The bit rate is = ', Round(FBitrate));           //  128 Round((1.277159348E+02))
          L_Bits := (L_Bytes * 8); writeln('Total Bits: ', L_Bits); //  Total Bits: 223268320
          writeln('The play time is = ', Trunc(L_Bits / 128000));   //  1744 seconds
          EC := BASS_ErrorGetCode();  write('Error Code = ', EC);     // 0
     end
     else
     begin
          writeln('False = H is ', H);    //
          EC := BASS_ErrorGetCode();  writeln('Error Code = ', EC);writeln();writeln(); // 0 meaning BASS_OK
     end;
     BASS_StreamFree(H); // close the stream and free the resources  {**}
     readln();
end.

What this code reveals is that using
Code: [Select]
BASS_ChannelGetAttribute(H,BASS_ATTRIB_BITRATE, FBitrate);  //
 writeln('The bit rate is = ', Round(FBitrate));           //  128 Round((1.277159348E+02))
is not useful.  When used with the bytes it produces a play time of 1744 seconds.  Data from File explorer says the play time is 2 mins and 37 secs.  That same accurate playtime is arrived at via
Code: [Select]
L_Seconds := BASS_ChannelBytes2Seconds(H, L_Bytes);
Wow.  The improved functionality and accuracy of this aspect of my play list writer means I have nearly replaced Creative Centrale as my tool of choice.

There are many lessons here.  One is that a previous contribution was so poorly written and without explanation that although it was the solution it was not understood: "about your Init error at first"
Code: [Select]
procedure TForm1.FormCreate(Sender: TObject);
begin
  Bass_Init(-1,44100,0,handle,nil);
end;

But an explanation, which is what I wanted, was never forthcoming

Thanks to everyone who helped

Ian @ un4seen

  • Administrator
  • Posts: 24047
Re: BASS and Lazarus
« Reply #17 on: 2 Dec '21 - 15:30 »
What this code reveals is that using
Code: [Select]
BASS_ChannelGetAttribute(H,BASS_ATTRIB_BITRATE, FBitrate);  //
 writeln('The bit rate is = ', Round(FBitrate));           //  128 Round((1.277159348E+02))
is not useful.  When used with the bytes it produces a play time of 1744 seconds.  Data from File explorer says the play time is 2 mins and 37 secs.  That same accurate playtime is arrived at via
Code: [Select]
L_Seconds := BASS_ChannelBytes2Seconds(H, L_Bytes);

BASS_ChannelGetLength gives the decoded length of the file, while the bitrate relates to the file's encoded data, so it isn't possible to combine them to calculate the file's duration in seconds. You would need to combine the bitrate with the file size to get the duration. That will only be approximate though, so it's better to use BASS_ChannelBytes2Seconds with the BASS_ChannelGetLength value instead.