Draw a waveform with Purebasic and BASS library

Just starting out? Need help? Post your questions and find answers here.
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4636
Joined: Sun Apr 12, 2009 6:27 am

Re: Draw a waveform with Purebasic and BASS library

Post by RASHAD »

Hi Martin
The data are the curve peaks
Search the forum for how to draw Spline between points
Good luck
Egypt my love
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Draw a waveform with Purebasic and BASS library

Post by wilbert »

Here's a very simple example just to show how you can draw the wave from sample data.
It does no checks at all at the moment so it assumes the audio is 16 bit stereo, it assumes there's enough memory to load the entire song and it assumes the song is long enough to fill the entire width of the CanvasGadget.
The idea is simply to get the min and max values of a number of samples and use those values to connect the dots.

Code: Select all

Structure Sample
  l.w
  r.w
EndStructure

BASS_Init(-1, 44100, 0, 0, #Null)

Channel.l = BASS_StreamCreateFile(#False, @"Test.mp3", 0, 0, #BASS_STREAM_PRESCAN|#BASS_STREAM_DECODE|#BASS_UNICODE)
Length.q = BASS_ChannelGetLength(Channel, #BASS_POS_BYTE)


SamplesPerPoint = 8

Dim Buffer.Sample(Length >> 2)
BASS_ChannelGetData(Channel, @Buffer(), Length)

OpenWindow(0, 0, 0, 620, 320, "Waveform", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CanvasGadget(0, 10, 10, 600, 300)

StartDrawing(CanvasOutput(0))
Box(0, 0, 600, 300, $e0e0e0)
PrevMinL.w = 0 : PrevMaxL.w = 0
PrevMinR.w = 0 : PrevMaxR.w = 0
For i = 0 To 599
  MinL.w = Buffer(i*SamplesPerPoint)\l : MaxL.w = MinL
  MinR.w = Buffer(i*SamplesPerPoint)\r : MaxR.w = MinR
  j = 1
  While j < SamplesPerPoint
    If Buffer(i*SamplesPerPoint + j)\l < MinL : MinL = Buffer(i*SamplesPerPoint + j)\l : EndIf
    If Buffer(i*SamplesPerPoint + j)\l > MaxL : MaxL = Buffer(i*SamplesPerPoint + j)\l : EndIf
    If Buffer(i*SamplesPerPoint + j)\r < MinR : MinR = Buffer(i*SamplesPerPoint + j)\r : EndIf
    If Buffer(i*SamplesPerPoint + j)\r > MaxR : MaxR = Buffer(i*SamplesPerPoint + j)\r : EndIf
    j + 1
  Wend
  If PrevMinL > MaxL : LineXY(i, 75 - PrevMinL>>9, i, 75 - MaxL>>9, $ff8000) : EndIf 
  If PrevMaxL < MinL : LineXY(i, 75 - PrevMaxL>>9, i, 75 - MinL>>9, $ff8000) : EndIf 
  If PrevMinR > MaxR : LineXY(i, 225 - PrevMinR>>9, i, 225 - MaxR>>9, $ff8000) : EndIf 
  If PrevMaxR < MinR : LineXY(i, 225 - PrevMaxR>>9, i, 225 - MinR>>9, $ff8000) : EndIf 
  LineXY(i, 75 - MinL>>9, i, 75 - MaxL>>9, $ff8000)  
  LineXY(i, 225 - MinR>>9, i, 225 - MaxR>>9, $ff8000)  
  PrevMinL = MinL : PrevMaxL = MaxL
  PrevMinR = MinR : PrevMaxR = MaxR
Next  
StopDrawing()

Repeat
  Event = WaitWindowEvent()
Until Event = #PB_Event_CloseWindow
Windows (x64)
Raspberry Pi OS (Arm64)
Martin Verlaan
Enthusiast
Enthusiast
Posts: 124
Joined: Sun Apr 01, 2018 11:26 am

Re: Draw a waveform with Purebasic and BASS library

Post by Martin Verlaan »

Hi Wilbert,

Whoohoo, this is what I need! If I run your example I see a zoomed view. I noticed that I can change the length of the waveform with SamplesPerPoint. What formula should I use to show the complete song as a waveform? And how can I zoom in to a specific start position and length?

It's cool to see that more people here working with the BASS Library. I am curious what kind of audio software has been made by Purebasic developers.

wilbert wrote:Here's a very simple example just to show how you can draw the wave from sample data.
It does no checks at all at the moment so it assumes the audio is 16 bit stereo, it assumes there's enough memory to load the entire song and it assumes the song is long enough to fill the entire width of the CanvasGadget.
The idea is simply to get the min and max values of a number of samples and use those values to connect the dots.

Code: Select all

Structure Sample
  l.w
  r.w
EndStructure

BASS_Init(-1, 44100, 0, 0, #Null)

Channel.l = BASS_StreamCreateFile(#False, @"Test.mp3", 0, 0, #BASS_STREAM_PRESCAN|#BASS_STREAM_DECODE|#BASS_UNICODE)
Length.q = BASS_ChannelGetLength(Channel, #BASS_POS_BYTE)


SamplesPerPoint = 8

Dim Buffer.Sample(Length >> 2)
BASS_ChannelGetData(Channel, @Buffer(), Length)

OpenWindow(0, 0, 0, 620, 320, "Waveform", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CanvasGadget(0, 10, 10, 600, 300)

StartDrawing(CanvasOutput(0))
Box(0, 0, 600, 300, $e0e0e0)
PrevMinL.w = 0 : PrevMaxL.w = 0
PrevMinR.w = 0 : PrevMaxR.w = 0
For i = 0 To 599
  MinL.w = Buffer(i*SamplesPerPoint)\l : MaxL.w = MinL
  MinR.w = Buffer(i*SamplesPerPoint)\r : MaxR.w = MinR
  j = 1
  While j < SamplesPerPoint
    If Buffer(i*SamplesPerPoint + j)\l < MinL : MinL = Buffer(i*SamplesPerPoint + j)\l : EndIf
    If Buffer(i*SamplesPerPoint + j)\l > MaxL : MaxL = Buffer(i*SamplesPerPoint + j)\l : EndIf
    If Buffer(i*SamplesPerPoint + j)\r < MinR : MinR = Buffer(i*SamplesPerPoint + j)\r : EndIf
    If Buffer(i*SamplesPerPoint + j)\r > MaxR : MaxR = Buffer(i*SamplesPerPoint + j)\r : EndIf
    j + 1
  Wend
  If PrevMinL > MaxL : LineXY(i, 75 - PrevMinL>>9, i, 75 - MaxL>>9, $ff8000) : EndIf 
  If PrevMaxL < MinL : LineXY(i, 75 - PrevMaxL>>9, i, 75 - MinL>>9, $ff8000) : EndIf 
  If PrevMinR > MaxR : LineXY(i, 225 - PrevMinR>>9, i, 225 - MaxR>>9, $ff8000) : EndIf 
  If PrevMaxR < MinR : LineXY(i, 225 - PrevMaxR>>9, i, 225 - MinR>>9, $ff8000) : EndIf 
  LineXY(i, 75 - MinL>>9, i, 75 - MaxL>>9, $ff8000)  
  LineXY(i, 225 - MinR>>9, i, 225 - MaxR>>9, $ff8000)  
  PrevMinL = MinL : PrevMaxL = MaxL
  PrevMinR = MinR : PrevMaxR = MaxR
Next  
StopDrawing()

Repeat
  Event = WaitWindowEvent()
Until Event = #PB_Event_CloseWindow
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Draw a waveform with Purebasic and BASS library

Post by wilbert »

Martin Verlaan wrote:Whoohoo, this is what I need! If I run your example I see a zoomed view. I noticed that I can change the length of the waveform with SamplesPerPoint. What formula should I use to show the complete song as a waveform? And how can I zoom in to a specific start position and length?
You can try something like this

Code: Select all

Structure Sample
  l.w
  r.w
EndStructure

Procedure UpdateWaveImage(PBImage, *SampleData.Sample, SampleCount)
  Protected.i Width, Height, X, VOffsetL, VOffsetR, Mul
  Protected.i SamplesPerPixel, Sample
  Protected.w MinL, MaxL, MinR, MaxR
  Protected.w MinL_, MaxL_, MinR_, MaxR_
  If *SampleData And SampleCount
    StartDrawing(ImageOutput(PBImage))
    Width = OutputWidth() : Height = OutputHeight()
    VOffsetL = Height >> 2 : VOffsetR = VOffsetL + Height >> 1
    Mul = (VOffsetL * $19999) >> 16
    Box(0, 0, Width, Height, $ffe0e0e0)
    Line(0, VOffsetL, Width, 1, $ffa0a0a0)
    Line(0, VOffsetR, Width, 1, $ffa0a0a0)
    SamplesPerPixel = (SampleCount + Width - 1) / Width
    MinL = $7fff : MaxL = $8000 : MinR = $7fff : MaxR = $8000
    MinL_ = $8000 : MaxL_ = $7fff : MinR_ = $8000 : MaxR_ = $7fff
    While SampleCount
      If *SampleData\l < MinL : MinL = *SampleData\l : ElseIf *SampleData\l > MaxL : MaxL = *SampleData\l : EndIf
      If *SampleData\r < MinR : MinR = *SampleData\r : ElseIf *SampleData\r > MaxR : MaxR = *SampleData\r : EndIf
      *SampleData + 4
      Sample + 1 : SampleCount - 1
      If Sample = SamplesPerPixel Or SampleCount = 0
        MinL = (MinL * Mul) >> 16 : MaxL = (MaxL * Mul) >> 16
        MinR = (MinR * Mul) >> 16 : MaxR = (MaxR * Mul) >> 16
        If MinL_ > MaxL : LineXY(X, VOffsetL - MinL_, X, VOffsetL - MaxL, $ffff8000) : EndIf 
        If MaxL_ < MinL : LineXY(X, VOffsetL - MaxL_, X, VOffsetL - MinL, $ffff8000) : EndIf 
        If MinR_ > MaxR : LineXY(X, VOffsetR - MinR_, X, VOffsetR - MaxR, $ffff8000) : EndIf 
        If MaxR_ < MinR : LineXY(X, VOffsetR - MaxR_, X, VOffsetR - MinR, $ffff8000) : EndIf 
        LineXY(X, VOffsetL - MinL, X, VOffsetL - MaxL, $ffff8000)
        LineXY(X, VOffsetR - MinR, X, VOffsetR - MaxR, $ffff8000)
        MinL_ = MinL : MaxL_ = MaxL : MinR_ = MinR : MaxR_ = MaxR
        MinL = $7fff : MaxL = $8000 : MinR = $7fff : MaxR = $8000
        X + 1 : Sample = 0
      EndIf
    Wend
    StopDrawing()
  EndIf  
EndProcedure


BASS_Init(-1, 44100, 0, 0, #Null)

Channel.l = BASS_StreamCreateFile(#False, @"Test.mp3", 0, 0, #BASS_STREAM_PRESCAN|#BASS_STREAM_DECODE|#BASS_UNICODE)
Length.q = BASS_ChannelGetLength(Channel, #BASS_POS_BYTE)

Dim Buffer.Sample(Length >> 2)
BASS_ChannelGetData(Channel, @Buffer(), Length)

OpenWindow(0, 0, 0, 620, 320, "Waveform", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)

WaveImage = CreateImage(#PB_Any, 8192, 256)
UpdateWaveImage(WaveImage, @Buffer(), Length >> 2)

ScrollAreaGadget(0, 10, 10, 600, 300, ImageWidth(WaveImage), ImageHeight(WaveImage), 10, #PB_ScrollArea_Center)
ImageGadget(1, 0, 0, 600, 300, ImageID(WaveImage))
CloseGadgetList()

Repeat
  Event = WaitWindowEvent()
Until Event = #PB_Event_CloseWindow
Make sure to compile with debugger disabled or it will be slow.
It could be made much faster using asm but not everyone likes that. So far it's PB code only.
By Creating a different size image, or altering the starting point and sample count for the UpdateWaveImage procedure, you can change the output.
Windows (x64)
Raspberry Pi OS (Arm64)
Martin Verlaan
Enthusiast
Enthusiast
Posts: 124
Joined: Sun Apr 01, 2018 11:26 am

Re: Draw a waveform with Purebasic and BASS library

Post by Martin Verlaan »

Thanks a lot for your help Wilbert :!: :!:
User avatar
CELTIC88
Enthusiast
Enthusiast
Posts: 154
Joined: Thu Sep 17, 2015 3:39 pm

Re: Draw a waveform with Purebasic and BASS library

Post by CELTIC88 »

thank you DR @wilbert,

very good code .

But the picture of waveform appears wrong

Image
interested in Cybersecurity..
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4636
Joined: Sun Apr 12, 2009 6:27 am

Re: Draw a waveform with Purebasic and BASS library

Post by RASHAD »

Hi CELTIC88
Can you post the name of the sound file you been testing?
Egypt my love
User avatar
CELTIC88
Enthusiast
Enthusiast
Posts: 154
Joined: Thu Sep 17, 2015 3:39 pm

Re: Draw a waveform with Purebasic and BASS library

Post by CELTIC88 »

hi @RASHAD :D

Egypt lost vs Uruguay :( , good luck in next match

https://a.uguu.se/k8ErarzJCDUZ_notify.wav
interested in Cybersecurity..
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Draw a waveform with Purebasic and BASS library

Post by wilbert »

The problem is that it's a mono file while my code is expecting a stereo file.
I'm not very familiar with BASS. It would be nice if there would be an option that converts mono automatically to two channels so it reads as stereo but I don't know if BASS offers that. :?
Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
CELTIC88
Enthusiast
Enthusiast
Posts: 154
Joined: Thu Sep 17, 2015 3:39 pm

Re: Draw a waveform with Purebasic and BASS library

Post by CELTIC88 »

AH OK :)

you can use "BASS_ChannelGetInfo" to detect number of channel ?
interested in Cybersecurity..
Martin Verlaan
Enthusiast
Enthusiast
Posts: 124
Joined: Sun Apr 01, 2018 11:26 am

Re: Draw a waveform with Purebasic and BASS library

Post by Martin Verlaan »

Code: Select all

  
Define Info.BASS_CHANNELINFO
Bass_ChannelGetInfo(Channel, @Info)    
Debug Info\chans ; 1=mono, 2=stereo, etc. 
Martin Verlaan
Enthusiast
Enthusiast
Posts: 124
Joined: Sun Apr 01, 2018 11:26 am

Re: Draw a waveform with Purebasic and BASS library

Post by Martin Verlaan »

@Wilbert, your code works great but when I zoom in a lot, the waveform looks distorted. It happens with all my files. Any idea what could be the reason of this? This is a view of 0.145 seconds
Image
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Draw a waveform with Purebasic and BASS library

Post by wilbert »

Martin Verlaan wrote:@Wilbert, your code works great but when I zoom in a lot, the waveform looks distorted. It happens with all my files. Any idea what could be the reason of this? This is a view of 0.145 seconds
I'm not sure what is causing it.
You could try to remove the lines below and see what is happening then.

Code: Select all

If MinL_ > MaxL : LineXY(X, VOffsetL - MinL_, X, VOffsetL - MaxL, $ffff8000) : EndIf 
If MaxL_ < MinL : LineXY(X, VOffsetL - MaxL_, X, VOffsetL - MinL, $ffff8000) : EndIf 
If MinR_ > MaxR : LineXY(X, VOffsetR - MinR_, X, VOffsetR - MaxR, $ffff8000) : EndIf 
If MaxR_ < MinR : LineXY(X, VOffsetR - MaxR_, X, VOffsetR - MinR, $ffff8000) : EndIf 
Those lines are to connect the previous and current line if there's a gap so you might end up with gaps if you remove those lines but it's just to see if the other problem goes away.
Windows (x64)
Raspberry Pi OS (Arm64)
Martin Verlaan
Enthusiast
Enthusiast
Posts: 124
Joined: Sun Apr 01, 2018 11:26 am

Re: Draw a waveform with Purebasic and BASS library

Post by Martin Verlaan »

Unfortunately that does not help. Maybe it's a limitation or BASS, I will ask on the BASS forum.
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: Draw a waveform with Purebasic and BASS library

Post by wilbert »

Martin Verlaan wrote:Unfortunately that does not help. Maybe it's a limitation or BASS, I will ask on the BASS forum.
Can you try with this updated procedure ?

Code: Select all

Procedure UpdateWaveImage(PBImage, *SampleData.Sample, SampleCount)
  Protected.i Width, Height, X, VOffsetL, VOffsetR, Mul
  Protected.i SamplesPerPixel, Sample
  Protected.w MinL, MaxL, MinR, MaxR
  If *SampleData And SampleCount
    StartDrawing(ImageOutput(PBImage))
    Width = OutputWidth() : Height = OutputHeight()
    VOffsetL = Height >> 2 : VOffsetR = VOffsetL + Height >> 1
    Mul = (VOffsetL * $19999) >> 16
    Box(0, 0, Width, Height, $ffe0e0e0)
    Line(0, VOffsetL, Width, 1, $ffa0a0a0)
    Line(0, VOffsetR, Width, 1, $ffa0a0a0)
    SamplesPerPixel = (SampleCount + Width - 1) / Width
    If SampleCount
      MinL = *SampleData\l : MinR = *SampleData\r : MaxL = MinL : MaxR = MinR      
      While SampleCount
        If *SampleData\l < MinL : MinL = *SampleData\l : ElseIf *SampleData\l > MaxL : MaxL = *SampleData\l : EndIf
        If *SampleData\r < MinR : MinR = *SampleData\r : ElseIf *SampleData\r > MaxR : MaxR = *SampleData\r : EndIf
        Sample + 1 : SampleCount - 1
        If Sample = SamplesPerPixel Or SampleCount = 0
          MinL = (MinL * Mul) >> 16 : MaxL = (MaxL * Mul) >> 16
          MinR = (MinR * Mul) >> 16 : MaxR = (MaxR * Mul) >> 16
          LineXY(X, VOffsetL - MinL, X, VOffsetL - MaxL, $ffff8000)
          LineXY(X, VOffsetR - MinR, X, VOffsetR - MaxR, $ffff8000)
          MinL = *SampleData\l : MinR = *SampleData\r : MaxL = MinL : MaxR = MinR
          X + 1 : Sample = 0
        EndIf
        *SampleData + 4
      Wend
    EndIf
    StopDrawing()
  EndIf  
EndProcedure
Windows (x64)
Raspberry Pi OS (Arm64)
Post Reply