Re: Draw a waveform with Purebasic and BASS library
Posted: Mon Jun 18, 2018 10:49 am
Thanks Wilbert!! I should test it some more but your updated code seems to work! You just saved me from a depression 

http://www.purebasic.com
https://www.purebasic.fr/english/
Code: Select all
;Coder Dr wilbert
;add support for mono channels
Structure Sample
l.w
r.w
EndStructure
IncludeFile "bass.pbi"
Procedure UpdateWaveImage(PBImage, *SampleData.Sample, SampleCount,Numchans)
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 >> Numchans : VOffsetR = VOffsetL + Height >> 1
MUL = (VOffsetL * $19999) >> 16
Box(0, 0, Width, Height, $ffe0e0e0)
Line(0, VOffsetL, Width, 1, $ffa0a0a0)
If Numchans = 2 :Line(0, VOffsetR, Width, 1, $ffa0a0a0):EndIf
SamplesPerPixel = (SampleCount + Width - 1) / Width
SampleSize = Numchans * 2
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)
If Numchans = 2 :LineXY(X, VOffsetR - MinR, X, VOffsetR - MaxR, $ffff8000):EndIf
MinL = *SampleData\l : MinR = *SampleData\r : MaxL = MinL : MaxR = MinR
X + 1 : Sample = 0
EndIf
*SampleData + SampleSize
Wend
EndIf
StopDrawing()
EndIf
EndProcedure
BASS_Init(-1, 44100, 0, 0, #Null)
Channel.l = BASS_StreamCreateFile(#False, @"notify.wav", 0, 0, #BASS_STREAM_PRESCAN|#BASS_STREAM_DECODE|#BASS_UNICODE)
Length.q = BASS_ChannelGetLength(Channel, #BASS_POS_BYTE)
Info.BASS_CHANNELINFO
Bass_ChannelGetInfo(Channel, @Info)
Dim Buffer.Sample(Length >> Info\chans)
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 >> Info\chans,Info\chans)
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
ThanksCELTIC88 wrote:re good code, is better than audacity for me and more fast
I added support for mono channels, after your permission @wilbert...
That looks great !Martin Verlaan wrote:I managed to build a simple wave viewer with zoom and marker options. Very happy now
Code: Select all
Procedure.q MinMaxScaled(*MinMaxArray, SamplesPerElement, *SampleData, SampleCount,
Scale = 48, Stereo = #True, Cont.q=$800080007fff7fff)
; <<< MinMaxScaled >>>
; This mmx optimized procedure fills an array with scaled min/max values.
; A scale of 64 means the output values will be in range [-64, 63].
; Max scale is 128 so each value fit in a byte [-128, 127].
Protected.i reg_b
If (*MinMaxArray And SamplesPerElement > 0) And (*SampleData And SampleCount > 0)
If Scale > 128 : Scale = 128 : EndIf
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!mov [p.v_reg_b], rbx ; backup rbx
!mov rax, [p.p_SampleData]
!mov rdx, [p.p_MinMaxArray]
CompilerElse
!mov [p.v_reg_b], ebx ; backup ebx
!mov eax, [p.p_SampleData]
!mov edx, [p.p_MinMaxArray]
CompilerEndIf
!mov ecx, [p.v_SamplesPerElement]
!movq mm0, [p.v_Cont] ; load continuation value
!pcmpeqw mm3, mm3 ; create inversion mask
!psrlq mm3, 32
!mov ebx, [p.v_Scale] ; load scale value
!imul ebx, 0x00020002
!movd mm4, ebx
!punpckldq mm4, mm4
!.l0:
!cmp ecx, [p.v_SampleCount] ; compare SamplesPerElement with SampleCount
!jbe .l1
!mov ecx, [p.v_SampleCount]
!.l1:
!cmp dword [p.v_Stereo], 0 ; stereo / mono check
!jnz .l2
; load mono sample
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movzx ebx, word [rax]
!add rax, 2
CompilerElse
!movzx ebx, word [eax]
!add eax, 2
CompilerEndIf
!movd mm1, ebx
!punpcklwd mm1, mm1
!jmp .l3
!.l2:
; load stereo sample
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movd mm1, [rax]
!add rax, 4
CompilerElse
!movd mm1, [eax]
!add eax, 4
CompilerEndIf
!.l3:
!punpckldq mm1, mm1 ; duplicate sample into l-r-l-r
!movq mm2, mm0
!pcmpgtw mm2, mm1 ; compare min/max with new sample
!pxor mm2, mm3
!pand mm0, mm2 ; update min/max
!pandn mm2, mm1
!por mm0, mm2
!sub ecx, 1
!jnz .l1
!pmulhw mm0, mm4 ; scale min/max values
!packsswb mm0, mm0 ; convert min/max from word to byte
; store scaled min/max values
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movd [rdx], mm0
!add rdx, 4
CompilerElse
!movd [edx], mm0
!add edx, 4
CompilerEndIf
!movq mm0, mm1
!mov ecx, [p.v_SamplesPerElement]
!sub [p.v_SampleCount], ecx ; decrease SampleCount
!ja .l0
!movq [p.v_Cont], mm1 ; store continuation value
!emms
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!mov rbx, [p.v_reg_b] ; restore rbx
CompilerElse
!mov ebx, [p.v_reg_b] ; restore ebx
CompilerEndIf
EndIf
ProcedureReturn Cont
EndProcedure
Structure MinMax
min.b[2]
max.b[2]
EndStructure
Procedure UpdateCanvas(Canvas, Array MM.MinMax(1), Offset = 0)
Protected.i Width, Height, VOffsetL, VOffsetR, MaxX, X, X_
StartDrawing(CanvasOutput(Canvas))
Width = OutputWidth() : Height = OutputHeight()
VOffsetL = Height >> 2 : VOffsetR = VOffsetL + Height >> 1
Box(0, 0, Width, Height, $ffe0e0e0)
Line(0, VOffsetL, Width, 1, $ffa0a0a0)
Line(0, VOffsetR, Width, 1, $ffa0a0a0)
X_ = Offset : MaxX = ArraySize(MM())
While X < Width And X <= MaxX
LineXY(X, VOffsetL - MM(X_)\min[0], X, VOffsetL - MM(X_)\max[0], $ffff8000)
LineXY(X, VOffsetR - MM(X_)\min[1], X, VOffsetR - MM(X_)\max[1], $ffff8000)
X + 1 : X_ + 1
Wend
StopDrawing()
EndProcedure
SamplesPerElement = 64
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)
NumSamples.q = Length >> 2
*WaveData = AllocateMemory(Length)
BASS_ChannelGetData(Channel, *WaveData, Length)
Dim MM.MinMax(NumSamples / SamplesPerElement)
MinMaxScaled(@MM(), SamplesPerElement, *WaveData, NumSamples, 52)
OpenWindow(0, 0, 0, 620, 360, "Waveform", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CanvasGadget(0, 10, 10, 600, 300)
ButtonGadget(1, 20, 320, 40, 20, "<<")
ButtonGadget(2, 70, 320, 40, 20, ">>")
ButtonGadget(3, 120, 320, 40, 20, "+")
ButtonGadget(4, 170, 320, 40, 20, "-")
UpdateCanvas(0, MM())
Offset = 0
Repeat
Event = WaitWindowEvent()
If Event = #PB_Event_Gadget
Select EventGadget()
Case 1
Offset - 600
If Offset < 0 : Offset = 0 : EndIf
Case 2
Offset + 600
Case 3
If SamplesPerElement > 1
SamplesPerElement >> 1 : Offset << 1
EndIf
ReDim MM.MinMax(NumSamples / SamplesPerElement)
MinMaxScaled(@MM(), SamplesPerElement, *WaveData, NumSamples, 52)
Case 4
If SamplesPerElement < 65536
SamplesPerElement << 1 : Offset >> 1
EndIf
ReDim MM.MinMax(NumSamples / SamplesPerElement)
MinMaxScaled(@MM(), SamplesPerElement, *WaveData, NumSamples, 52)
EndSelect
UpdateCanvas(0, MM(), Offset)
EndIf
Until Event = #PB_Event_CloseWindow
Code: Select all
Procedure.l MinMaxScaled(*MinMaxArray, SamplesPerElement, *SampleData, SampleCount,
Scale = 48, Stereo = #True, Cont.l=0)
; <<< MinMaxScaled >>>
; This sse2 optimized procedure fills an array with scaled min/max values.
; A scale of 64 means the output values will be in range [-64, 63].
; Max scale is 128 so each value fit in a byte [-128, 127].
If (*MinMaxArray And SamplesPerElement > 0) And (*SampleData And SampleCount > 0)
If Scale > 128 : Scale = 128 : EndIf
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!mov [rsp-8], rbx ; backup rbx
!movdqu [rsp-24], xmm6 ; backup xmm6
!movdqu [rsp-40], xmm7 ; backup xmm7
!mov rax, [p.p_SampleData]
!mov rdx, [p.p_MinMaxArray]
CompilerElse
!mov [esp-4], ebx ; backup ebx
!mov eax, [p.p_SampleData]
!mov edx, [p.p_MinMaxArray]
CompilerEndIf
!mov ecx, [p.v_SamplesPerElement]
!mov ebx, 1 ; set xmm6 to 1/SamplesPerElement
!cvtsi2sd xmm6, ebx
!cvtsi2sd xmm1, ecx
!divsd xmm6, xmm1
!movlhps xmm6, xmm6
!mov ebx, [p.v_Scale] ; set xmm7 to scale value
!imul ebx, 0x00020002
!movd xmm7, ebx
!pshufd xmm7, xmm7, 0
!movd xmm0, [p.v_Cont] ; load continuation value
!.l0:
!movdqa xmm2, xmm0
!movdqa xmm3, xmm0
!xorpd xmm4, xmm4
!xorpd xmm5, xmm5
!cmp ecx, [p.v_SampleCount] ; compare SamplesPerElement with SampleCount
!jbe .l1
!mov ebx, 1
!mov ecx, [p.v_SampleCount]
!cvtsi2sd xmm6, ebx
!cvtsi2sd xmm1, ecx
!divsd xmm6, xmm1
!movlhps xmm6, xmm6
!.l1:
!cmp dword [p.v_Stereo], 0 ; stereo / mono check
!jnz .l2
; load mono sample
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movzx ebx, word [rax]
!add rax, 2
CompilerElse
!movzx ebx, word [eax]
!add eax, 2
CompilerEndIf
!movd xmm0, ebx
!punpcklwd xmm0, xmm0
!jmp .l3
!.l2:
; load stereo sample
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movd xmm0, [rax]
!add rax, 4
CompilerElse
!movd xmm0, [eax]
!add eax, 4
CompilerEndIf
!.l3:
!movdqa xmm1, xmm0
!punpcklwd xmm1, xmm1 ; convert word to long
!psrad xmm1, 16
!cvtdq2pd xmm1, xmm1 ; convert long to double
!pminsw xmm2, xmm0 ; update min
!pmaxsw xmm3, xmm0 ; update max
!addpd xmm4, xmm1 ; update sum
!mulpd xmm1, xmm1
!addpd xmm5, xmm1 ; update sumsq
!sub ecx, 1
!jnz .l1
!mulpd xmm4, xmm6 ; divide sum by samples per element
!mulpd xmm5, xmm6 ; divide sumsq by samples per element
!sqrtpd xmm5, xmm5 ; square root of sumsq
!cvtpd2dq xmm4, xmm4 ; convert to long
!cvtpd2dq xmm5, xmm5
!packssdw xmm4, xmm4 ; convert to word
!packssdw xmm5, xmm5
!punpckldq xmm2, xmm3
!punpckldq xmm4, xmm5
!punpcklqdq xmm2, xmm4 ; combine min/max/avg/rms
!pmulhw xmm2, xmm7 ; scale values
!packsswb xmm2, xmm2 ; convert from to byte
; store scaled values
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movq [rdx], xmm2
!add rdx, 8
CompilerElse
!movq [edx], xmm2
!add edx, 8
CompilerEndIf
!mov ecx, [p.v_SamplesPerElement]
!sub [p.v_SampleCount], ecx ; decrease SampleCount
!ja .l0
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movdqu xmm7, [rsp-40] ; restore xmm7
!movdqu xmm6, [rsp-24] ; restore xmm6
!mov rbx, [rsp-8] ; restore rbx
CompilerElse
!mov ebx, [esp-4] ; restore ebx
CompilerEndIf
EndIf
!movd eax, xmm0 ; return continuation value
ProcedureReturn
EndProcedure
Structure MinMax
min.b[2]
max.b[2]
avg.b[2]
rms.a[2]
EndStructure
Procedure UpdateCanvas(Canvas, Array MM.MinMax(1), Offset = 0, RMS = #False)
Protected.i Width, Height, VOffsetL, VOffsetR, MaxX, X, X_
StartDrawing(CanvasOutput(Canvas))
Width = OutputWidth() : Height = OutputHeight()
VOffsetL = Height >> 2 : VOffsetR = VOffsetL + Height >> 1
X_ = Offset : MaxX = ArraySize(MM())
Box(0, 0, Width, Height, $ffe0e0e0)
If RMS
Line(0, VOffsetL, Width, 1, $ffa0a0a0)
Line(0, VOffsetR, Width, 1, $ffa0a0a0)
While X < Width And X_ <= MaxX
LineXY(X, VOffsetL, X, VOffsetL - MM(X_)\rms[0], $ffc0c000)
LineXY(X, VoffsetR, X, VOffsetR - MM(X_)\rms[1], $ffc0c000)
X + 1 : X_ + 1
Wend
Else
Line(0, VOffsetL, Width, 1, $ffa0a0a0)
Line(0, VOffsetR, Width, 1, $ffa0a0a0)
While X < Width And X_ <= MaxX
LineXY(X, VOffsetL - MM(X_)\min[0], X, VOffsetL - MM(X_)\max[0], $ffffc000)
LineXY(X, VOffsetR - MM(X_)\min[1], X, VOffsetR - MM(X_)\max[1], $ffffc000)
Plot(X, VOffsetL - MM(X_)\avg[0], $ffff8000)
Plot(X, VOffsetR - MM(X_)\avg[1], $ffff8000)
X + 1 : X_ + 1
Wend
EndIf
StopDrawing()
EndProcedure
SamplesPerElement = 64
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)
NumSamples.q = Length >> 2
*WaveData = AllocateMemory(Length)
BASS_ChannelGetData(Channel, *WaveData, Length)
Dim MM.MinMax(NumSamples / SamplesPerElement)
MinMaxScaled(@MM(), SamplesPerElement, *WaveData, NumSamples, 64)
OpenWindow(0, 0, 0, 620, 360, "Waveform", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CanvasGadget(0, 10, 10, 600, 300)
ButtonGadget(1, 20, 320, 40, 20, "<<")
ButtonGadget(2, 70, 320, 40, 20, ">>")
ButtonGadget(3, 120, 320, 40, 20, "+")
ButtonGadget(4, 170, 320, 40, 20, "-")
CheckBoxGadget(5, 250, 320, 60, 20, "RMS")
StringGadget(6, 500, 320, 100, 20, "0.000")
Offset = 0
UpdateCanvas(0, MM(), Offset, GetGadgetState(5))
Repeat
Event = WaitWindowEvent()
If Event = #PB_Event_Gadget
Select EventGadget()
Case 1
Offset - 600
If Offset < 0 : Offset = 0 : EndIf
Case 2
Offset + 600
Case 3
If SamplesPerElement > 1
SamplesPerElement >> 1 : Offset << 1
EndIf
ReDim MM.MinMax(NumSamples / SamplesPerElement)
MinMaxScaled(@MM(), SamplesPerElement, *WaveData, NumSamples, 64)
Case 4
If SamplesPerElement < 65536
SamplesPerElement << 1 : Offset >> 1
EndIf
ReDim MM.MinMax(NumSamples / SamplesPerElement)
MinMaxScaled(@MM(), SamplesPerElement, *WaveData, NumSamples, 64)
Case 6
Offset = ValD(GetGadgetText(6))*44100/SamplesPerElement
UpdateCanvas(0, MM(), Offset, GetGadgetState(5))
Continue
EndSelect
UpdateCanvas(0, MM(), Offset, GetGadgetState(5))
SetGadgetText(6, StrD(Offset*SamplesPerElement/44100, 3))
EndIf
Until Event = #PB_Event_CloseWindow
Code: Select all
Procedure LoadWaveform()
Define Filename.s = GetGadgetText(FileString)
Chan = BASS_StreamCreateFile(#False, @Filename, 0, 0, #BASS_STREAM_PRESCAN|#BASS_UNICODE) ;global variable
Channel = BASS_StreamCreateFile(#False, @Filename, 0, 0, #BASS_STREAM_PRESCAN|#BASS_STREAM_DECODE|#BASS_UNICODE) ;global variable
Length = BASS_ChannelGetLength(Channel, #BASS_POS_BYTE) ;global variable
BytesPerPixel = Length / #WaveformXPixels ;global variable
ReDim Buffer.Sample(Length >> 2);global variable
BytesRead = 0 ;global variable
BufTel = 0 ;global variable
Part = 1024 * 40 ;global variable
Procent = 0 ;global variable
StartDrawing(CanvasOutput(WaveformPic))
Box(0, 0, #WaveformXPixels, WaveformYPixels, RGB(0, 0, 0))
DrawText(290, (WaveformYPixels / 2) - 10, "Loading waveform... (0%)", RGB(255, 255, 255), RGB(0, 0, 0))
StopDrawing()
If Not BufTimerActive
AddWindowTimer(dbWindow, #BufTimer, 1)
AddWindowTimer(dbWindow, #ProcentTimer, 250)
EndIf
EndProcedure
Code: Select all
Repeat
Event = WaitWindowEvent()
If EventWindow() = dbWindow
If Event = #PB_Event_Timer
Select EventTimer()
Case #ProcentTimer
Procent = Int(100 * (BytesRead / Length))
StartDrawing(CanvasOutput(WaveformPic))
DrawText(290, (WaveformYPixels / 2) - 10, "Loading waveform... (" + Procent + "%)", RGB(255, 255, 255), RGB(0, 0, 0))
StopDrawing()
Case #BufTimer
BufTimerActive = #True
ReDim Buf.Sample(Part >> 2)
Bytes = BASS_ChannelGetData(Channel, @Buf(), part)
BytesRead + bytes
For i = 0 To Bytes - 1
Buffer(BufTel >> 2) = Buf(i >> 2)
BufTel + 1
Next i
If BytesRead >= Length
RemoveWindowTimer(dbWindow, #BufTimer)
RemoveWindowTimer(dbWindow, #ProcentTimer)
BufTimerActive = #False
WaveImage = CreateImage(#PB_Any, #WaveformXPixels, WaveformYPixels)
UpdateWaveImage(@Buffer(), Length >> 2)
DrawMarkers()
Define EndSec.d = BASS_ChannelBytes2Seconds(Channel, Length)
SetGadgetText(SelBeginString, SecondsToTime(0))
SetGadgetText(SelEndString, SecondsToTime(0))
SetGadgetText(SelLengthString, SecondsToTime(0))
SetGadgetText(ViewBeginString, SecondsToTime(0))
SetGadgetText(ViewEndString, SecondsToTime(EndSec))
SetGadgetText(ViewLengthString, SecondsToTime(EndSec))
SetGadgetText(CursorPosString, SecondsToTime(0))
DisableGadget(Button_ZoomToSelection, #True)
DisableGadget(Button_ZoomOut, #True)
DisableGadget(Button_ZoomOutFull, #True)
DisableGadget(ButtonPlay, #False)
DisableGadget(Combo_Markers, #False)
DisableGadget(ButtonMarker, #False)
DisableGadget(ButtonDeleteMarker, #False)
DisableGadget(Button_Resize, #False)
CursorPos = 0
DrawCursor()
EndIf
Case #PlayTimer
If GetGadgetText(SelLengthString) <> "00:00.000"
Define StopPosition.q = BASS_ChannelSeconds2Bytes(Chan, Time2Seconds(GetGadgetText(SelEndString)))
Else
Define StopPosition.q = BASS_ChannelSeconds2Bytes(Chan, Time2Seconds(GetGadgetText(ViewEndString)))
EndIf
If BASS_ChannelGetPosition(Chan, #BASS_POS_BYTE) >= StopPosition
StopPlaying()
Else
Define CurrentPosBytes.q = BASS_ChannelGetPosition(Chan, #BASS_POS_BYTE)
Define CurrentPosSec.d = BASS_ChannelBytes2Seconds(Chan, CurrentPosBytes)
Define PosBeginBytes.q = BASS_ChannelSeconds2Bytes(Chan, Time2Seconds(GetGadgetText(ViewBeginString)))
SetGadgetText(CursorPosString, SecondsToTime(CurrentPosSec))
StartDrawing(CanvasOutput(WaveformPic))
If GetGadgetText(SelLengthString) <> "00:00.000"
DrawImage(ImageID(#SelectionWaveImage), 0, 0)
Line((CurrentPosBytes - PosBeginBytes) / BytesPerPixel, 0, 1, WaveformYPixels, RGB(0, 0, 0))
Else
DrawImage(ImageID(#CursorWaveImage), 0, 0)
Line((CurrentPosBytes - PosBeginBytes) / BytesPerPixel, 0, 1, WaveformYPixels, RGB(255, 255, 255))
EndIf
StopDrawing()
EndIf
EndSelect
EndIf
dbWindow_Events(Event)
EndIf
Until Event = #PB_Event_CloseWindow
Martin Verlaan wrote:Nice (and very interesting) surprise Wilbert! Your previous code was already pretty fast but the faster the better. I am struggeling to understand how to use your updated code. How to show the full waveform and is it also possible to show a part based on a begin and end position?
Code: Select all
Procedure.q MinMaxScaled(*MinMaxArray, SamplesPerElement, *SampleData, SampleCount, Scale = 48, Stereo = #True, Cont.q=$800080007fff7fff)
Code: Select all
IncludeFile "include/bass.pbi"
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
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
BASS_Init(-1, 44100, 0, 0, #Null)
filename.s = "/media/martin/Extern/smartmix-Data/mp3/100 bpm/casbl 6400731 cube - two heads are better than one [time 5m55s, kbps 128, bpm 100.81, year 1982, genre italodisco].mp3"
Channel = BASS_StreamCreateFile(#False, @filename, 0, 0, #BASS_STREAM_PRESCAN|#BASS_STREAM_DECODE|#BASS_UNICODE)
BASS_ChannelSetPosition(Channel, BASS_ChannelSeconds2Bytes(Channel, 200.360), #BASS_POS_BYTE)
length = BASS_ChannelSeconds2Bytes(Channel, 0.500)
Dim Buffer.Sample(length >> 2)
BASS_ChannelGetData(Channel, @Buffer(), length)
OpenWindow(0, 0, 0, 620, 320, "Pos: 3:20.360 - Length: 0.500", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
WaveImage = CreateImage(#PB_Any, 600, 300)
UpdateWaveImage(WaveImage, @Buffer(), length >> 2)
ImageGadget(1, 10, 0, 600, 300, ImageID(WaveImage))
Repeat
Event = WaitWindowEvent()
Until Event = #PB_Event_CloseWindow
Code: Select all
Procedure UpdateWaveImage(PBImage, *SampleData.Sample, SampleCount, Mode=0)
; Mode 0 : Min/max
; Mode 1 : RMS
Protected.i Width, Height, X, VOffsetL, VOffsetR, Mul
Protected.i SamplesPerPixel, Sample
Protected.w MinL, MaxL, MinR, MaxR
Protected.d SumsqL, SumsqR, Rcp
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
If Mode = 0
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
Else
Rcp = 1.0 / SamplesPerPixel
While SampleCount
SumsqL + (*SampleData\l * *SampleData\l) : SumsqR + (*SampleData\r * *SampleData\r)
Sample + 1 : SampleCount - 1
If Sample = SamplesPerPixel Or SampleCount = 0
If SampleCount = 0 : Rcp = 1.0 / Sample : EndIf
MaxL = Sqr(SumsqL * Rcp) : MaxR = Sqr(SumsqR * Rcp)
MaxL = (MaxL * Mul) >> 16 : MaxR = (MaxR * Mul) >> 16
LineXY(X, VOffsetL, X, VOffsetL - MaxL, $ffff8000)
LineXY(X, VOffsetR, X, VOffsetR - MaxR, $ffff8000)
SumsqL = 0 : SumsqR = 0
X + 1 : Sample = 0
EndIf
*SampleData + 4
Wend
EndIf
EndIf
StopDrawing()
EndIf
EndProcedure
It's an mp3 file. As a test I saved it to WAV and tried again. The waveform looks correct then, exactly the same as Cool Edit. So BASS_ChannelSetPosition seems not 100% accurate with mp3 files. I also tried the BASS_POS_DECODETO flag but without success. If you still want the MP3 file, then let me know...Wolfram wrote:@ Martin
can you send me the wav file which is shown in the pictures above.
Martin Verlaan wrote:It's an mp3 file. As a test I saved it to WAV and tried again. The waveform looks correct then, exactly the same as Cool Edit. So BASS_ChannelSetPosition seems not 100% accurate with mp3 files. I also tried the BASS_POS_DECODETO flag but without success. If you still want the MP3 file, then let me know...Wolfram wrote:@ Martin
can you send me the wav file which is shown in the pictures above.