Comparing the posted waveforms, it looks your one is slightly ahead of the Cool Edit one. I suspect that is because BASS is removing the encoding delay (and padding) from the decoded data while Cool Edit isn't. Which one is correct is probably a matter of opinion but I would say the former is more correct. For example, if you try generating and encoding a sine wave to MP3 with LAME and then load that into Cool Edit, you will probably see a little gap added at the start and end. Those gaps won't be present if you decode the MP3 with BASS because it removes them.
Draw a waveform with Purebasic and BASS library
-
- Enthusiast
- Posts: 133
- Joined: Sun Apr 01, 2018 11:26 am
Re: Draw a waveform with Purebasic and BASS library
Yes, I posted a message on the BASS forum and I got this explanation:
-
- Enthusiast
- Posts: 133
- Joined: Sun Apr 01, 2018 11:26 am
Re: Draw a waveform with Purebasic and BASS library
To genius Wilbert:
I am using your latest assembly code. It's fast but there's still a small delay when I zoom in or zoom out, because every time I need to call BASS_ChannelGetData(). Like this:
There must be a faster way to access the needed waveform-data without calling BASS_ChannelGetData() everytime. Do you have an idea how I can do this?
I am using your latest assembly code. It's fast but there's still a small delay when I zoom in or zoom out, because every time I need to call BASS_ChannelGetData(). Like this:
Code: Select all
Length = BASS_ChannelSeconds2Bytes(Channel, AfterPos - BeforePos)
NumSamples.q = Length >> 2
SamplesPerElement = (NumSamples / WaveformXPixels)
BytesPerPixel = SamplesPerElement << 2
*WaveData = AllocateMemory(Length)
BASS_ChannelSetPosition(Channel, PosBytes, #BASS_POS_BYTE)
BASS_ChannelGetData(Channel, *WaveData, Length)
Dim MM.MinMax(NumSamples / SamplesPerElement)
MinMaxScaled(@MM(), SamplesPerElement, *WaveData, NumSamples)
UpdateCanvas(MM())
Re: Draw a waveform with Purebasic and BASS library
The fastest way is to keep the entire song in memory at the maximum zoom-in level and zoom out from there.Martin Verlaan wrote:There must be a faster way to access the needed waveform-data without calling BASS_ChannelGetData() everytime. Do you have an idea how I can do this?
It's important in this case to think about what accuracy you need.
Sample accurate means about 10 megabyte of data per minute to store which is quite a lot if you keep the entire song in memory.
If it's enough for you to have an accuracy of about a half or one millisecond per pixel you would have to store much less data and it's faster when zooming.
Once you have that entire song in memory as an array of min/max values, I could write another asm procedure to zoom out from that to the level you want to display (like the entire song).
Windows (x64)
Raspberry Pi OS (Arm64)
Raspberry Pi OS (Arm64)
-
- Enthusiast
- Posts: 133
- Joined: Sun Apr 01, 2018 11:26 am
Re: Draw a waveform with Purebasic and BASS library
I think the accuracy is only important when zooming in deeply. So I would really appreciate if you could write such procedure (if it's not to much hassle for you)! That would make zooming with the mouse wheel also faster. Thanks in advance!wilbert wrote:Martin Verlaan wrote:Once you have that entire song in memory as an array of min/max values, I could write another asm procedure to zoom out from that to the level you want to display (like the entire song).
Re: Draw a waveform with Purebasic and BASS library
Can you try if the MMCombine procedure below work for you ?Martin Verlaan wrote:I think the accuracy is only important when zooming in deeply. So I would really appreciate if you could write such procedure (if it's not to much hassle for you)! That would make zooming with the mouse wheel also faster. Thanks in advance!
Code: Select all
Procedure MMCombine(*MMSrc, *MMDst, SrcElements, Combine); SSE
; load xor mask into mm2
!mov eax, 0x807f807f
!movd mm2, eax
!punpcklbw mm2, mm2
; load *MMSrc and *MMDst pointers
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!mov rax, [p.p_MMSrc]
!mov rdx, [p.p_MMDst]
CompilerElse
!mov eax, [p.p_MMSrc]
!mov edx, [p.p_MMDst]
CompilerEndIf
!mov ecx, [p.v_Combine]
; verify if Combine <= SrcElements
!.l0:
!cmp ecx, [p.v_SrcElements]
!jbe .l1
; if not, set Combine to SrcElements
!mov ecx, [p.v_SrcElements]
!mov [p.v_Combine], ecx
!.l1:
!pxor mm0, mm0
!test ecx, 1
!jz .l2
!add ecx, 1
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movd mm1, [rax]
!add rax, 4
CompilerElse
!movd mm1, [eax]
!add eax, 4
CompilerEndIf
!pshufw mm1, mm1, 01000100b
!jmp .l3
!.l2:
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movq mm1, [rax]
!add rax, 8
CompilerElse
!movq mm1, [eax]
!add eax, 8
CompilerEndIf
!.l3:
!pxor mm1, mm2
!pmaxub mm0, mm1
!sub ecx, 2
!jnz .l2
!pshufw mm1, mm0, 1110b
!pmaxub mm0, mm1
!pxor mm0, mm2
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movd [rdx], mm0
!add rdx, 4
CompilerElse
!movd [edx], mm0
!add edx, 4
CompilerEndIf
!mov ecx, [p.v_Combine]
!sub [p.v_SrcElements], ecx
!jnz .l0
!emms
EndProcedure
; >> Test <<
Structure MinMax
min.b[2]
max.b[2]
EndStructure
Dim MM.MinMax(4)
Dim MM_Zoomed.MinMax(2)
MM(0)\min[0] = -5
MM(0)\min[1] = -9
MM(0)\max[0] = 25
MM(0)\max[1] = 75
MM(1)\min[0] = -15
MM(1)\min[1] = -19
MM(1)\max[0] = 45
MM(1)\max[1] = 95
MM(2)\min[0] = -1
MM(2)\min[1] = -89
MM(2)\max[0] = 77
MM(2)\max[1] = 1
MM(3)\min[0] = 0
MM(3)\min[1] = 1
MM(3)\max[0] = 0
MM(3)\max[1] = 3
; Combine 2 MinMax values into 1
MMCombine(@MM(), @MM_Zoomed(), 4, 2)
Debug MM_Zoomed(0)\min[0]
Debug MM_Zoomed(0)\min[1]
Debug MM_Zoomed(0)\max[0]
Debug MM_Zoomed(0)\max[1]
Debug MM_Zoomed(1)\min[0]
Debug MM_Zoomed(1)\min[1]
Debug MM_Zoomed(1)\max[0]
Debug MM_Zoomed(1)\max[1]
- The first procedure argument (*MMSrc) needs to be the address of the source array.
This would be the array of MinMax values of the entire song in it's most expanded state. - The second argument is the address of the destination array.
- The third argument is the number of elements the source array contains.
- The last argument is how many source elements to combine into one destination element.
Windows (x64)
Raspberry Pi OS (Arm64)
Raspberry Pi OS (Arm64)
-
- Enthusiast
- Posts: 133
- Joined: Sun Apr 01, 2018 11:26 am
Re: Draw a waveform with Purebasic and BASS library
First of all: Thank you!
Sorry, I find it difficult to understand your explanation.
The way I zoom without your new procedure was like this:
How can I make above code work with your procedure?
Sorry, I find it difficult to understand your explanation.
The way I zoom without your new procedure was like this:
Code: Select all
PosBytes.q = BASS_ChannelSeconds2Bytes(Channel, BeforePos)
Length.q = BASS_ChannelSeconds2Bytes(Channel, AfterPos - BeforePos)
NumSamples.q = Length >> 2
SamplesPerElement = (NumSamples / WaveformXPixels)
*WaveData = AllocateMemory(Length)
BASS_ChannelSetPosition(Channel, PosBytes, #BASS_POS_BYTE)
BASS_ChannelGetData(Channel, *WaveData, Length)
Dim MM.MinMax(NumSamples / SamplesPerElement)
MinMaxScaled(@MM(), SamplesPerElement, *WaveData, NumSamples)
UpdateCanvas(MM())
Re: Draw a waveform with Purebasic and BASS library
Unfortunately it's not one simple change.Martin Verlaan wrote:How can I make above code work with your procedure?
The main problem is with BASS_ChannelGetData because it has to decode every time. That's why I suggested to store the entire decoded mp3 in memory.
The fastest way is to do that when it comes to working with the data afterwards, is as an array of min/max values but it would also be possible to do it as decoded samples if there's enough memory.
But you probably would have to change your code in other places as well for this to load an entire song and work from that data instead of using BASS_ChannelGetData all the time.
If you don't want to do that, there's still a little room to improve the speed but not much as the most time consuming thing is probably the mp3 decode.
Windows (x64)
Raspberry Pi OS (Arm64)
Raspberry Pi OS (Arm64)
-
- Enthusiast
- Posts: 133
- Joined: Sun Apr 01, 2018 11:26 am
Re: Draw a waveform with Purebasic and BASS library
No problem, I'm going to play with it and try to implement it. I don't give up
Re: Draw a waveform with Purebasic and BASS library
Hi Wilbert,
can you show me how the MinMaxScaled() procedure must look if I change the structure to word?
can you show me how the MinMaxScaled() procedure must look if I change the structure to word?
Code: Select all
Structure MinMax
min.W[2]
max.W[2]
EndStructure
wilbert wrote:That looks great !Martin Verlaan wrote:I managed to build a simple wave viewer with zoom and marker options. Very happy now
I was working on an alternative way using a min/max array to draw but it seems you probably won't need it.
I will post it anyway; maybe someone else can use it.
The idea was to create an array of min max values so the drawing of the wave would be faster when you want to update the waveform while playing audio.
But maybe the previous approach was already fast enough for that. I didn't try that.
MMX VersionCode: 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
More advanced SSE2 versionCode: 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
macOS Catalina 10.15.7
Re: Draw a waveform with Purebasic and BASS library
Are you looking for the same kind of input (16 bit audio) but a bigger output scale ?Wolfram wrote:can you show me how the MinMaxScaled() procedure must look if I change the structure to word?
Windows (x64)
Raspberry Pi OS (Arm64)
Raspberry Pi OS (Arm64)
Re: Draw a waveform with Purebasic and BASS library
Yes, if I want a bigger amplitude (Y zoom) I need words.wilbert wrote:Are you looking for the same kind of input (16 bit audio) but a bigger output scale ?Wolfram wrote:can you show me how the MinMaxScaled() procedure must look if I change the structure to word?
And I store the values in the array as final Y1 and Y2 values for the draw Line() function.
Code: Select all
MinMaxArray(px)\y = centerOfCanvas -minValue
MinMaxArray(px)\hight = maxValue +minValue
macOS Catalina 10.15.7
Re: Draw a waveform with Purebasic and BASS library
Can you try if the code below works for you ?Wolfram wrote:Yes, if I want a bigger amplitude (Y zoom) I need words.
And I store the values in the array as final Y1 and Y2 values for the draw Line() function.
Scale can now be between -16384 and 16383. I also added offset values for left and right.
So you could let the procedure calculate the final Y coordinates for you by adding an offset value and inverting the scale (like -300) because of the need for vertically flipped coordinates.
Code: Select all
Procedure.q MinMaxScaled(*MinMaxArray, SamplesPerElement, *SampleData, SampleCount,
Scale = 48, Stereo = #True, OffsetL = 0, OffsetR = 0,
Cont.q=$800080007fff7fff)
; <<< MinMaxScaled >>>
; This mmx optimized procedure fills an array with scaled min/max values.
; Scale should be in range [-16384, 16383].
; A scale of 64 means the output values will be in range [-64, 63].
Protected.i reg_b
If (*MinMaxArray And SamplesPerElement > 0) And (*SampleData And SampleCount > 0)
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
!movd mm4, [p.v_Scale] ; load scale value
!paddw mm4, mm4
!punpcklwd mm4, mm4
!punpckldq mm4, mm4
!movd mm5, [p.v_OffsetL] ; load offset values
!movd mm6, [p.v_OffsetR]
!punpcklwd mm5, mm6
!punpckldq mm5, mm5
!.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
!paddw mm0, mm5 ; add offset
; store scaled min/max values
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movq [rdx], mm0
!add rdx, 8
CompilerElse
!movq [edx], mm0
!add edx, 8
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.w[2]
max.w[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, #True)
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
Windows (x64)
Raspberry Pi OS (Arm64)
Raspberry Pi OS (Arm64)
Re: Draw a waveform with Purebasic and BASS library
I tried to remove the stereo option as well, but get no luck. For me assembler is hard to read.wilbert wrote:Can you try if the code below works for you ?Wolfram wrote:Yes, if I want a bigger amplitude (Y zoom) I need words.
And I store the values in the array as final Y1 and Y2 values for the draw Line() function.
Scale can now be between -16384 and 16383. I also added offset values for left and right.
So you could let the procedure calculate the final Y coordinates for you by adding an offset value and inverting the scale (like -300) because of the need for vertically flipped coordinates.
Code: Select all
Structure MinMax
min.w
max.w
EndStructure
Procedure.q MinMaxScaled(*MinMaxArray, SamplesPerElement, *SampleData, SampleCount,
Scale = 120, OffsetL =150, Cont.q=$800080007fff7fff)
; <<< MinMaxScaled >>>
; This mmx optimized procedure fills an array with scaled min/max values.
; Scale should be in range [-16384, 16383].
; A scale of 64 means the output values will be in range [-64, 63].
Protected.i reg_b
If (*MinMaxArray And SamplesPerElement > 0) And (*SampleData And SampleCount > 0)
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
!movd mm4, [p.v_Scale] ; load scale value
!paddw mm4, mm4
!punpcklwd mm4, mm4
!punpckldq mm4, mm4
!movd mm5, [p.v_OffsetL] ; load offset values
; !movd mm6, [p.v_OffsetR]
; !punpcklwd mm5, mm6
; !punpckldq mm5, mm5
!.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, 2
;
; CompilerElse
;
; !movd mm1, [eax]
; !add eax, 2
; 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
!paddw mm0, mm5 ; add offset
; store scaled min/max values
CompilerIf #PB_Compiler_Processor = #PB_Processor_x64
!movq [rdx], mm0
!add rdx, 4
CompilerElse
!movq [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
macOS Catalina 10.15.7
Re: Draw a waveform with Purebasic and BASS library
The code I posted can handle a mono input with the Stereo argument set to #False and on that case outputs identical values for left and right channel.Wolfram wrote:I tried to remove the stereo option as well, but get no luck. For me assembler is hard to read.
So you could draw only one of those channels in that case.
If you want the output to be also one channel, that can be changed but in that case the structure also needs to be changed to
Code: Select all
Structure MinMax
min.W
max.W
EndStructure
Windows (x64)
Raspberry Pi OS (Arm64)
Raspberry Pi OS (Arm64)
Re: Draw a waveform with Purebasic and BASS library
Ohhh, I got it!wilbert wrote:The code I posted can handle a mono input with the Stereo argument set to #False and on that case outputs identical values for left and right channel.Wolfram wrote:I tried to remove the stereo option as well, but get no luck. For me assembler is hard to read.
So you could draw only one of those channels in that case.
If you want the output to be also one channel, that can be changed but in that case the structure also needs to be changed toCode: Select all
Structure MinMax min.W max.W EndStructure
My thoughts went in a different direction.
Last question, whats is the secret of the Cont.q=$800080007fff7fff.
Can you give me an PB code snipe which shows how it work?
macOS Catalina 10.15.7