How to design the Waveform (and be the King)

Started by fredvs,

fredvs

Who will be the king who explain me how to design the waveform (like in Goldwave, Cakewalk, etc) just before the stream is playing.

I just know that i have to use Stream_Decode and Getdata,
to find the volume at some positions...

Then it is easy, put this values in array and zis done.

OK for position (with setposition),... But for getdata ?????

bigjim

Hi, I've posted an example in delphi below.
My code decodes a stream to an array zoomed to a certain amount but you get the look your after by grabbing the largest and smallest sample from each block of samples you get and storing it in the array. You can then plot the array at play-time.
The codes not commented but is fairly self explanitory. The vrThreads are a component which can be emulated by using a timer or a loop set to release to the system every so often.

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Bass,
  StdCtrls, ExtCtrls, ComCtrls, VrControls, VrThreads, VrTrackBar,
  VrDisplay;

type
  TForm1 = class(TForm)
    Button1: TButton;
    VrThread1: TVrThread;
    Image13: TPaintBox;
    VrThread2: TVrThread;
    ProgressBar1: TProgressBar;
    Timer1: TTimer;
    TrackBar1: TVrTrackBar;
    Button2: TButton;
    Button3: TButton;
    ScrollBar1: TScrollBar;
    OpenDialog1: TOpenDialog;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure VrThread1Execute(Sender: TObject);
    procedure VrThread2Execute(Sender: TObject);
    procedure TrackBar1MouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure TrackBar1MouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Timer1Timer(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure ScrollBar1Change(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;
  mp3h : DWORD;
  mp3h2 : DWORD;
  decodeb : array[0..4410] of smallint;
  wavpos : integer;
  zoomwave_max : array of smallint;
  zoomwave_min : array of smallint;
  zoomtime : array of single;
  countb : integer;
  imagpos : integer;
  image1 : TBitmap;
  moving : boolean;
  zoomvert : integer;
  totalmax, totalmin : smallint;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
var
maxlength : integer;
i : integer;
begin
if opendialog1.execute = true then
begin
  moving := false;
  Bass_Init(-1 , 44100, 0, form1.handle);
  Bass_Start;
  mp3h := Bass_StreamCreateFile(FALSE, pChar(opendialog1.filename), 0, 0, BASS_STREAM_DECODE OR BASS_MP3_SETPOS);
  mp3h2 := Bass_StreamCreateFile(FALSE, pChar(opendialog1.filename), 0, 0, 0);
  image1 := TBitmap.Create;
  image1.width := image13.width;
  image1.height := image13.height;
  totalmax := 0;
  totalmin := 0;
  vrThread1.Enabled := true;
  maxlength := trunc(bass_streamgetlength(mp3h) / 4410);
  setlength(zoomwave_max, maxlength+1);
  setlength(zoomwave_min, maxlength+1);
  setlength(zoomtime, maxlength+1);
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
widtha : integer;
begin
wavpos := 1;
bass_streamplay(mp3h2, FALSE, 0);
timer1.enabled := true;
vrThread2.Enabled := true;
end;

procedure TForm1.VrThread1Execute(Sender: TObject);
var
i,j : integer;
maxval, minval : smallint;
begin
if Bass_ChannelGetPosition(mp3h) < Bass_StreamGetLength(mp3h) then
begin
  maxval := 0;
  minval := 0;
  ProgressBar1.Position := trunc((Bass_ChannelGetPosition(mp3h) / Bass_StreamGetLength(mp3h))*100);
  Bass_ChannelGetData(mp3h, @decodeb, 4410);
  for i := 0 to 2205 do
  begin
    if decodeb[i*2] > maxval then maxval := decodeb[i*2];
    if decodeb[i*2] < minval then minval := decodeb[i*2];
  end;
if totalmax < maxval then totalmax := maxval;
if totalmin > minval then totalmin := minval;
/////////////////
/////////////////
  zoomtime[wavpos] := Bass_ChannelBytes2Seconds(mp3h, Bass_ChannelGetPosition(mp3h));
  zoomwave_max[wavpos] := maxval;
  zoomwave_min[wavpos] := minval;
/////////////////
  wavpos := wavpos + 1;
end;
if Bass_ChannelGetPosition(mp3h) = Bass_StreamGetLength(mp3h) then
begin
  zoomvert := (totalmax + (-totalmin) div 2) div ((image13.height div 3) * 2);//div 80
  progressbar1.Visible := false;
  trackbar1.MaxValue := Trunc(Bass_ChannelBytes2Seconds(mp3h, Bass_StreamGetLength(mp3h)));
  trackbar1.position := 0;
  VrThread1.Enabled := false;
end;
end;

procedure TForm1.VrThread2Execute(Sender: TObject);
var
widtha : integer;
begin
if (Bass_ChannelBytes2Seconds(mp3h2, Bass_ChannelGetPosition(mp3h2)) >= zoomtime[countb]) and (Bass_ChannelBytes2Seconds(mp3h2, Bass_ChannelGetPosition(mp3h2)) < zoomtime[countb+1]) then
begin
  countb := countb + 1;
  Image1.Canvas.Brush.Color := $007C9885;
  Image1.Canvas.Rectangle(0, 0, Image1.Width, Image1.Height);
  Image1.Canvas.Pen.Color := clBlack;
  for widtha := 1 to image1.width do
  begin
    image1.Canvas.MoveTo(widtha, image1.height div 2);
    image1.Canvas.LineTo(widtha, (image1.height div 2) - (zoomwave_max[(widtha + countb)-image1.width div 2] div (zoomvert))); //100-zoomvert
    image1.Canvas.MoveTo(widtha, image1.height div 2);
    image1.Canvas.LineTo(widtha, (image1.height div 2) + (-zoomwave_min[(widtha + countb)-image1.width div 2] div (zoomvert)));
    imagpos := imagpos + 1;
  end;
  image1.Canvas.MoveTo(0, image1.height div 2);
  image1.Canvas.LineTo(image1.width-1, image1.height div 2);
  Image1.Canvas.Pen.Color := clWhite;
  image1.Canvas.MoveTo(image1.width div 2, 0);
  image1.Canvas.LineTo(image1.width div 2, image1.height);
  Image1.Canvas.Pen.Color := clBlack;
  BitBlt(image13.canvas.Handle, 0, 0, image13.width, image13.height, image1.Canvas.Handle, 0, 0, srccopy);
end;
end;

procedure TForm1.TrackBar1MouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
moving := true;
end;

procedure TForm1.TrackBar1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
var
oldpos : DWORD;
newpos : single;
begin
oldpos := Bass_ChannelGetPosition(mp3h2);
Bass_ChannelSetPosition(mp3h2, Bass_ChannelSeconds2Bytes(mp3h2, TrackBar1.position));
newpos := Bass_ChannelBytes2Seconds(mp3h2, Bass_ChannelGetPosition(mp3h2));
if Bass_ChannelGetPosition(mp3h2) > oldpos then
begin
  repeat
     countb := countb + 1;
  until zoomtime[countb] >= newpos;
end;
if Bass_ChannelGetPosition(mp3h2) < oldpos then
begin
  repeat
     countb := countb - 1;
  until zoomtime[countb] <= newpos;
end;
moving := false;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
if moving = false then TrackBar1.Position := Trunc(Bass_ChannelBytes2Seconds(mp3h2, Bass_ChannelGetPosition(mp3h2)));
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
Bass_ChannelPause(mp3h2);
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
Bass_ChannelResume(mp3h2);
end;

procedure TForm1.ScrollBar1Change(Sender: TObject);
begin
Bass_ChannelSetAttributes(mp3h2, 88201-Scrollbar1.position, -1, -101);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
vrthread2.Enabled := false;
zoomtime := nil;
zoomwave_max := nil;
zoomwave_min := nil;
image1.Destroy;
Bass_Free;
end;

end.

fredvs

#2
Sorry Jim, but you are already the Emperor (thanks to your beat-counter).
Then now you become the King of the Emperors. Congratulations... :-* :-*


Thank youuuuuuuuuuu Jim  :-*, you are the best (like bass).  


Ps : if i have well understand :
1 position is 4410 bytes, to know the volume, you search for the max and min value of each 2205 * i bytes (who represent a % volume of some left freq) and you get the general volume at that position  :idea:...


fredvs

#3
Jim, it works like a charm.

I have 2 waves-forms quick (1 for the stream playing, 1 for the cued), its perfect. (will see zoom and change position of/with trackbar later...)


 With your :
Bass_StreamCreateFile(FALSE, f, 0, 0, BASS_STREAM_DECODE OR BASS_MP3_SETPOS)...;

Plus your :
BASS_ChannelSetPosition... ;

And your superb :
Bass_ChannelGetData(strform, @decodeb, 4410);
 for i := 0 to 2205 do
 begin
   if decodeb[i*2] > maxval then maxval := decodeb[i*2];
   if decodeb[i*2] < minval then minval := decodeb[i*2];
 end;...

I have all what i wanted, my prog works like bass, perfectly,

Humm, the loop works better with a timer on 1 than with a do while... :-/

Thanks a lot Jim  :-*

Ingolf2

I have done the same, only store it in an array of signed bytes. That way you can zoom in and out on all the data that you have. Don't know how the above code in Delphi does this, didn't read it, but I saw some constant values, zo I guess there is no dynamic zooming there. Maybe someone is interested:

var
  FScaleVal: Integer = 4096;

FScaleVal specifies the samples to skip...

procedure TMixerForm.BuildWavePeaks(var Channel: TWaveChannel);
var
  J, C, Read, ToRead: Integer;
  R: Extended;
begin
  with FWaveData, Channel do begin
    R := High(ShortInt) / BitValues[BitDepth];
    Data.Seek(0, soBeginning);
    ToRead := ReadBufSize*ChSize;
    SetLength(Peaks, Samples);
    C := 0;
    repeat
      Read := Data.Read(DataBuf, ToRead) div ChSize;
      for J := 0 to Read-1 do begin
        Peaks[C] := Round(DataBuf[J] * R);
        Inc(C);
      end;
    until (Read <> ReadBufSize) or (C = Samples);
  end;
end;

... to build my peaks array, and then this to view it zoomed...

procedure TMixerForm.UpdateWave;
var
  I, P: Integer;
  R: Single;

  procedure DrawPeaks(var Channel: TWaveChannel; cY: Integer);
  var
    aP, aN: ShortInt;
    I, J: Integer;
    C: Single;
  begin
    with Channel, FWaveBmp.Canvas do begin
      if Length(Peaks) > 0 then begin
        C := 0;
        MoveTo(0,cY);
        for I := 0 to Round(Length(Peaks) / FScaleVal) do begin
          if I < FWaveBmp.Width then begin
            aP := 0;
            aN := 0;
            for J := Round(C) to Round(C+FScaleVal)-1 do begin
              if J < Length(Peaks) then begin
                if Peaks[J] < 0 then begin
                  if Peaks[J] < aN then aN := Peaks[J];
                end
                else begin
                  if Peaks[J] > aP then aP := Peaks[J];
                end;
              end;
            end;
            if (aP = 0) and (aN = 0) then LineTo(I,cY)
            else begin
              if aP <> 0 then LineTo(I,Round(cY-(aP*R)));
              if aN <> 0 then LineTo(I,Round(cY-(aN*R)));
            end;
            C := C + FScaleVal;
          end;
        end;
      end;
    end;
  end;

begin
  with FWaveBmp.Canvas, FWaveData do begin
    if TryLock then begin
      try
        Brush.Color := BackColor;
        FillRect(ClipRect);
        if Length(Channels) > 0 then P := FWaveBmp.Height div Length(Channels)
        else P := FWaveBmp.Height;
        R := ((P / 2)-1) / High(ShortInt);
        for I := 0 to High(Channels) do begin
          if I > 0 then begin
            Pen.Color := ChannelSplitColor;
            MoveTo(0,P*I);
            LineTo(FWaveBmp.Width,P*I);
          end;
          Pen.Color := ChannelCenterColor;
          MoveTo(0,(P*I)+(P div 2));
          LineTo(FWaveBmp.Width,(P*I)+(P div 2));
          Pen.Color := ChannelPartColor;
          MoveTo(0,(P*I)+(P div 4));
          LineTo(FWaveBmp.Width,(P*I)+(P div 4));
          Pen.Color := ChannelPartColor;
          MoveTo(0,(P*(I+1))-(P div 4));
          LineTo(FWaveBmp.Width,(P*(I+1))-(P div 4));
          Pen.Color := WaveColor;
          DrawPeaks(Channels[I], (P*I)+(P div 2));
        end;
      finally
        Unlock;
      end;
    end;
  end;
end;

fredvs

#5
Hello Folks.

With the codes of Jim, a quick way to design...

I use a canvas of 1000 * 200,
/////////////////////////////////////////////////////
Pulic var
strima : HSTREAM;
lenimage , x, maxval : longint ;
volume : array[1..1000] of integer; // for a canvas of length 1000 pixels (x)
decodeb : array[0..4410] of smallint;
//////////////////////////////////////////////////////
procedure Tmixk.Button1Click(Sender: TObject); // the first  procedure..
begin
strima :=  Bass_StreamCreateFile(FALSE, 'test.mp3', 0, 0, BASS_STREAM_DECODE OR BASS_MP3_SETPOS);
lenimage := round(BASS_StreamGetLength(strima)) ;
form1.PaintBox1.canvas.pen.width := 1 ;
x := 0; //or else for zoom
timer1.Enabled := true ; //start the loop
end;
end;
////////////////////////////////////////////////////////////
procedure Tmixk.Timer1Timer(Sender: TObject); // Timer1 on 1 msec, i can do it with do while, but with my amd 1800 it is not so quick... ???  
var
 i : integer ;
begin
Timer1.Enabled := false ;
if x > 1000  then
      begin
       BASS_ChannelStop(strima);
        BASS_StreamFree(strima);
  end
  else
begin
BASS_ChannelSetPosition(strima, round((x *  lenimage ) /1000)) ;  // /if you zoom change value of  lenimage
 Bass_ChannelGetData(strima, @decodeb, 4410);
 for i := 0 to 2205 do
 if decodeb[i*2] > maxval then maxval := decodeb[i*2];
 maxval := maxval div 300 ;/// i use a canvas of 1000 * 200, then maxval := maxval div 300 (it looks good)...
form1.PaintBox1.Canvas.PolyLine([Point(x,(maxval)div 2), Point(x,(maxval + (maxval)div 2))]); /// design the wave
volume[x] :=  maxval ; // array for refresh or zoom later
application.ProcessMessages; // Yield to system briefly
x1 := x + 1 ;
 Timer1.Enabled := true ;
 end;
end;
/////////////////////////////////////////////////////
procedure TForm1..Button2Click(Sender: TObject); // for refresh or zoom...
 var
 i : integer ;
begin
for i := 0 to 1000 do
form1.PaintBox1.Canvas.PolyLine([Point(i,(volume)div 2), Point(i,(volume + (volume)div 2))]);
 end;




Richard_b