PlaySound with a 20KHz file produces sound ?!

Just starting out? Need help? Post your questions and find answers here.
User avatar
idle
Always Here
Always Here
Posts: 6168
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: PlaySound with a 20KHz file produces sound ?!

Post by idle »

I will look at it tomorrow. The channels are only valid for 2 it was a quick cut n paste and adaption. if you set it to one channel it would half the frequency effectively slowing it down if you set it to 4 it'd sound like a chipmunk.
escape75
User
User
Posts: 23
Joined: Fri Jan 09, 2026 7:52 am

Re: PlaySound with a 20KHz file produces sound ?!

Post by escape75 »

Sounds good, the only 2 main changes were:

Code: Select all


  Samples = (DataSize / 2 / NumChannels) - 1   ; OLD: Samples = (DataSize / 2 / 2) - 1  

      For NumChannel = 1 To NumChannels        ; OLD: *audioPtr\u= tone  
        PokeW(*audioPtr, Tone)                 ; OLD: *audioPtr+2 
        *audioPtr + 2                          ; OLD: *audioPtr\u= tone  
      Next                                     ; OLD: *audioPtr+2 
      
Not sure if that's correct but seems to work ...
User avatar
idle
Always Here
Always Here
Posts: 6168
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: PlaySound with a 20KHz file produces sound ?!

Post by idle »

you'll know it's right by the sound, as in no clicks and right pitch. I'm not sure what I was doing with the alignment. :lol:

also should be

Code: Select all

 For NumChannel = 1 To NumChannels
        PokeW(*audioPtr, Tone)
        *audioPtr + 2
      Next
escape75
User
User
Posts: 23
Joined: Fri Jan 09, 2026 7:52 am

Re: PlaySound with a 20KHz file produces sound ?!

Post by escape75 »

Sometimes it helps for someone else to glance over the code to spot a mistake :)

I found out some clicks are caused by volume starting at 100% even though the sound should not be heard there's a click when beginning and ending playback ...

EDIT:

The For Next loop you specified was the same as mine, no? :)

Did you mean I shouldn't have changed:
Samples = (DataSize / 2 / NumChannels) - 1 ; OLD: Samples = (DataSize / 2 / 2) - 1
User avatar
idle
Always Here
Always Here
Posts: 6168
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: PlaySound with a 20KHz file produces sound ?!

Post by idle »

escape75 wrote: Sun Jan 11, 2026 9:43 pm Sometimes it helps for someone else to glance over the code to spot a mistake :)

I found out some clicks are caused by volume starting at 100% even though the sound should not be heard there's a click when beginning and ending playback ...
I can't hear any clicks with your code.

And yes it helps to have someone glance at code, I'm a dyslexic, I don't see mistakes in my own code.
escape75
User
User
Posts: 23
Joined: Fri Jan 09, 2026 7:52 am

Re: PlaySound with a 20KHz file produces sound ?!

Post by escape75 »

I think it's safe to say the code works :)
escape75
User
User
Posts: 23
Joined: Fri Jan 09, 2026 7:52 am

Re: PlaySound with a 20KHz file produces sound ?!

Post by escape75 »

I found an issue after analyzing the wave file and it seems the center frequency is 25460hz instead of 20000hz

I suspect the bug is around here somewhere:

Code: Select all

      Time = Sample / SamplingRate * 8
      Tone = Sin(Time * Frequency) * 32767 * Volume
The complete code with wave output, the program should be terminated right away as it generates the wave file that's needed quickly:

Code: Select all



EnableExplicit

Enumeration 100
  #Window
  #Timer
  #Sound
EndEnumeration

Global Counter = -1

Structure RIFFStructure
  ChunkID.a[4]
  ChunkSize.l
  Format.a[4]
EndStructure

Structure fmtStructure
  Subchunk1ID.a[4]
  Subchunk1Size.l
  AudioFormat.c
  NumChannels.c
  SampleRate.l
  ByteRate.l
  BlockAlign.c
  BitsPerSample.c
EndStructure

Structure dataStructure
  Subchunk2ID.a[4]
  Subchunk2Size.l
EndStructure

Procedure ErrorHandler()
  Define ErrorMessage$ = ErrorMessage() + "(" + ErrorLine() + ")"
  MessageRequester("Program Error", ErrorMessage$)
  End
EndProcedure

OnErrorCall(@ErrorHandler())

Procedure FixSysTrayIcon(hWnd, uMsg, wParam, lParam)
  If uMsg = #WM_POWERBROADCAST And wParam = #PBT_APMRESUMESUSPEND
    RemoveWindowTimer(#Window, #Timer)
    AddWindowTimer(#Window, #Timer, 6000)
    Counter = 5
  EndIf
  
  ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

Procedure CreateSound(Sound, Duration, BitsPerSample, NumChannels, SamplingRate, Frequency)
  
  Define HeaderSize, DataSize, Samples, Sample, Time.d, Tone.w, NumChannel
  Define *WAVBuffer, *RiffPtr.RIFFStructure, *fmtPtr.fmtStructure, *dataPtr.dataStructure, *audioPtr
  
  HeaderSize = SizeOf(RIFFStructure)
  HeaderSize + SizeOf(fmtStructure)
  HeaderSize + SizeOf(dataStructure)
  
  DataSize = (BitsPerSample/8) * SamplingRate * Duration * NumChannels / 1000
  Samples = (DataSize / 2 / NumChannels) - 1
  
  *WAVBuffer = AllocateMemory(HeaderSize + DataSize, #PB_Memory_NoClear)
  
  If *WAVBuffer
    
    *RiffPtr = *WAVBuffer
    PokeS(@*RiffPtr\ChunkID, "RIFF", 4, #PB_Ascii|#PB_String_NoZero)
    *RiffPtr\ChunkSize = HeaderSize + DataSize - 8
    PokeS(@*RiffPtr\Format, "WAVE", 4, #PB_Ascii|#PB_String_NoZero)
    
    *fmtPtr = *WAVBuffer + SizeOf(RIFFStructure)
    PokeS(@*fmtPtr\Subchunk1ID, "fmt ", 4, #PB_Ascii|#PB_String_NoZero)
    *fmtPtr\Subchunk1Size = SizeOf(fmtStructure) - 8
    *fmtPtr\AudioFormat = 1
    *fmtPtr\NumChannels = NumChannels
    *fmtPtr\SampleRate = SamplingRate
    *fmtPtr\BlockAlign = *fmtPtr\NumChannels * (*fmtPtr\BitsPerSample/8)
    *fmtPtr\ByteRate = *fmtPtr\SampleRate * *fmtPtr\BlockAlign
    *fmtPtr\BitsPerSample = BitsPerSample
    
    *dataPtr = *WAVBuffer + SizeOf(RIFFStructure) + SizeOf(fmtStructure)
    PokeS(@*dataPtr\Subchunk2ID, "data", 4, #PB_Ascii|#PB_String_NoZero)
    *dataPtr\Subchunk2Size = DataSize
    
    *audioPtr = *WAVBuffer + SizeOf(RIFFStructure) + SizeOf(fmtStructure) + SizeOf(dataStructure)
    
    Define Steps.d = 0.9 / (Samples*0.25)
    Define Volume.d = 0.0
    
    For Sample = 0 To Samples
      
      If Sample < (Samples*0.25)
        Volume + Steps
      EndIf
      If Sample > (Samples*0.75)
        Volume - Steps
      EndIf
      
      Time = Sample / SamplingRate * 8
      Tone = Sin(Time * Frequency) * 32767 * Volume
      
      For NumChannel = 1 To NumChannels
        PokeW(*audioPtr, Tone)
        *audioPtr + 2
      Next
      
    Next
    
  If CreateFile(0, GetHomeDirectory()+"\Desktop\Test.wav")
    WriteData(0, *WAVBuffer, HeaderSize + DataSize)
    CloseFile(0)
  EndIf
  
    CatchSound(Sound, *WAVBuffer)
    FreeMemory(*WAVBuffer)
    
  EndIf
  
EndProcedure

OpenWindow(#Window, 0, 0, 300, 70, "Wake Up", #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered)

TextGadget(123, 10, 25, 280, 40, "20000Hz Wake Up Test", #PB_Text_Center)

If InitSound()
  CreateSound(#Sound, 1000, 16, 1, 96000, 20000)
  PlaySound(#Sound) 
  AddWindowTimer(#Window, #Timer, 60000)
  SetWindowCallback(@FixSysTrayIcon(), #Window)
EndIf

Repeat
  
  Define Event = WaitWindowEvent(250)
  
  If Event = #PB_Event_Timer And EventTimer() = #Timer
    PlaySound(#Sound)
    If Counter = 0
      RemoveWindowTimer(#Window, #Timer)
      AddWindowTimer(#Window, #Timer, 60000)
    EndIf
    If Counter > -1
      Counter = Counter - 1
    EndIf
  EndIf
  
Until Event = #PB_Event_CloseWindow

When the sampling is set to 44100hz the center frequency changes to 18634hz
escape75
User
User
Posts: 23
Joined: Fri Jan 09, 2026 7:52 am

Re: PlaySound with a 20KHz file produces sound ?!

Post by escape75 »

I have made the following changes, maybe someone can double check if this was a correct fix:

From

Code: Select all

Time = Sample / SamplingRate * 8
Tone = Sin(Time * Frequency) * 32767 * Volume
To

Code: Select all

Tone = Sin(2 * #PI * Frequency * Sample / SamplingRate) * 32767 * Volume
User avatar
idle
Always Here
Always Here
Posts: 6168
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: PlaySound with a 20KHz file produces sound ?!

Post by idle »

yes that's what it should be.

Code: Select all

 time = sample / SamplingRate 
 tone = Sin(time * 2 * #PI * frequency) * 32767
escape75
User
User
Posts: 23
Joined: Fri Jan 09, 2026 7:52 am

Re: PlaySound with a 20KHz file produces sound ?!

Post by escape75 »

Ok perfect, that's what I have ...

Unfortnately I located another bug, with 24 bit generation, the 2 channels have different data, and playback is scrambled ... hmm!
User avatar
idle
Always Here
Always Here
Posts: 6168
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: PlaySound with a 20KHz file produces sound ?!

Post by idle »

escape75 wrote: Mon Jan 12, 2026 5:45 am Ok perfect, that's what I have ...

Unfortnately I located another bug, with 24 bit generation, the 2 channels have different data, and playback is scrambled ... hmm!
It needs modifying to do 24 and 32 bit so yes I will rewrite the procedure to handle variable bit sizes

Code: Select all

InitSound()
EnableExplicit 

Structure WavHeader
  chunkID.a[4]        ; "RIFF"
  chunkSize.l         ; File size - 8
  format.a[4]         ; "WAVE"
  subchunk1ID.a[4]    ; "fmt "
  subchunk1Size.l     ; 16 for PCM
  audioFormat.u       ; 1 = PCM
  numChannels.u
  sampleRate.l
  byteRate.l
  blockAlign.u
  bitsPerSample.u
  subchunk2ID.a[4]    ; "data"
  subchunk2Size.l     ; NumSamples * NumChannels * BitsPerSample/8
EndStructure 

Procedure.i CreateSound(Sound.i, Duration.i, Bits.i=16, Channels.i=2, SamplingRate.i=44100, frequency=500)
  Protected numSamples = SamplingRate * Duration / 1000
  Protected bytesPerSample = Bits / 8
  Protected dataSize = numSamples * Channels * bytesPerSample
  Protected *header.WAVHeader, *data 
  Protected *WAVBuffer = AllocateMemory(dataSize + SizeOf(WavHeader)) 
  
  If Not *WAVBuffer
    ProcedureReturn #False
  EndIf
  
  *header = *WAVBuffer  
  PokeS(@*header\chunkID[0], "RIFF", 4, #PB_Ascii | #PB_String_NoZero) 
  *header\chunkSize = 36 + dataSize
  PokeS(@*header\format[0], "WAVE", 4, #PB_Ascii | #PB_String_NoZero)
  PokeS(@*header\subchunk1ID, "fmt ", 4, #PB_Ascii | #PB_String_NoZero)
  *header\subchunk1Size = 16
  *header\audioFormat = 1    
  *header\numChannels = Channels
  *header\sampleRate = SamplingRate
  *header\byteRate = SamplingRate * Channels * bytesPerSample
  *header\blockAlign = Channels * bytesPerSample
  *header\bitsPerSample = Bits
  PokeS(@*header\subchunk2ID, "data", 4, #PB_Ascii | #PB_String_NoZero)
  *header\subchunk2Size = dataSize
  
  *data = *WAVBuffer + SizeOf(WavHeader) 
  
  Protected a,i,maxAmplitude
  Protected time.d,sample,result   
  
  Select Bits
    Case 16
      maxAmplitude = 32767
    Case 24
      maxAmplitude = 8388607     
    Case 32
      maxAmplitude = 2147483647   
    Default
      maxAmplitude = 32767
  EndSelect
  
  numSamples - 1
  For i = 0 To numSamples
    time = i / SamplingRate
    sample = maxAmplitude * Sin(2.0 * #PI * frequency * time)
    
    For a = 1 To Channels
      Select bytesPerSample          
        Case 2  
          PokeW(*data, sample) 
          *data + 2 
        Case 3  
          PokeB(*data, sample & $FF)              
          PokeB(*data + 1, (sample >> 8) & $FF)   
          PokeB(*data + 2, (sample >> 16) & $FF)  
          *data + 3 
        Case 4  
          PokeL(*data, sample) 
          *data + 4 
      EndSelect  
    Next 
  Next 
  
  Result = CatchSound(Sound, *WAVBuffer)
  FreeMemory(*WAVBuffer)
  
  ProcedureReturn Result
  
EndProcedure 

CreateSound(0, 1000, 24, 2, 96000, 1000) 
PlaySound(0) 
Delay(1000)
escape75
User
User
Posts: 23
Joined: Fri Jan 09, 2026 7:52 am

Re: PlaySound with a 20KHz file produces sound ?!

Post by escape75 »

Great, I was fixing it in a similar way but yours is a bit cleaner :)

Couple questions ...

Would memory de-allocate here cause any issues:

Code: Select all

CatchSound(Sound, *WAVBuffer)
FreeMemory(*WAVBuffer)
I was thinking make it a global at the start of the program and don't free until program exit, although PB might automatically free it anyways ...

Code: Select all

Global *WAVBuffer

Code: Select all

CatchSound(Sound, *WAVBuffer)
User avatar
idle
Always Here
Always Here
Posts: 6168
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: PlaySound with a 20KHz file produces sound ?!

Post by idle »

I'm not very good at explaining. Pb doesn't garbage collect memory you allocatememory with so it's better to minimize the memory lifetime and scope and in this case it's lifetime is done once it's copied to the sound object. So you free it.
escape75
User
User
Posts: 23
Joined: Fri Jan 09, 2026 7:52 am

Re: PlaySound with a 20KHz file produces sound ?!

Post by escape75 »

Ok that makes sense, although I've noticed that when you FreeMemory it just overwrites a few bytes at the beginning of the buffer
and you can actually make a second call to CatchSound afterwards and it will still play correctly as just the text strings are corrupted ...

This makes it hard to test whether the buffer is actually copied or just used as is :)

In any case here's the 2 examples I have, both work the same, but done in 2 different ways:

Code: Select all



EnableExplicit

Enumeration 100
  #Window
  #Sound
EndEnumeration

Global *WAVBuffer

Structure WAVFormat
  ChunkID.a[4]
  ChunkSize.l
  Format.a[4]
  Subchunk1ID.a[4]
  Subchunk1Size.l
  AudioFormat.w
  NumChannels.w
  SampleRate.l
  ByteRate.l
  BlockAlign.w
  BitsPerSample.w
  Subchunk2ID.a[4]
  Subchunk2Size.l
EndStructure

Procedure CreateSound(Sound, Duration, Frequency)
  
  Protected NumChannels = 1
  Protected BitsPerSample = 16
  Protected SamplingRate = 44100
    
  Protected Samples, DataSize, Sample
  Protected *fmtPtr.WAVFormat, *datPtr
 
  Samples = SamplingRate * Duration
  DataSize = Samples * NumChannels * (BitsPerSample/8)
  
  *WAVBuffer = AllocateMemory(SizeOf(WAVFormat) + DataSize)
  
  If *WAVBuffer
    
    *fmtPtr = *WAVBuffer
    PokeS(@*fmtPtr\ChunkID, "RIFF", 4, #PB_Ascii | #PB_String_NoZero)
    *fmtPtr\ChunkSize = 36 + DataSize
    PokeS(@*fmtPtr\Format, "WAVE", 4, #PB_Ascii | #PB_String_NoZero)
    PokeS(@*fmtPtr\Subchunk1ID, "fmt ", 4, #PB_Ascii | #PB_String_NoZero)
    *fmtPtr\Subchunk1Size = 16
    *fmtPtr\AudioFormat = 1
    *fmtPtr\NumChannels = NumChannels
    *fmtPtr\SampleRate = SamplingRate
    *fmtPtr\ByteRate = SamplingRate * NumChannels * (BitsPerSample/8)
    *fmtPtr\BlockAlign = NumChannels * (BitsPerSample/8)
    *fmtPtr\BitsPerSample = BitsPerSample
    PokeS(@*fmtPtr\Subchunk2ID, "data", 4, #PB_Ascii | #PB_String_NoZero)
    *fmtPtr\Subchunk2Size = DataSize
    
    *datPtr = *WAVBuffer + SizeOf(WAVFormat)
    
    For Sample = 1 To Samples
      PokeW(*datPtr, Sin(2 * #PI * Frequency * Sample / SamplingRate) * 32767)
      *datPtr + 2
    Next
    
    CatchSound(Sound, *WAVBuffer)
    
  EndIf
  
EndProcedure

OpenWindow(#Window, 0, 0, 400, 200, "Test", #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered)

If InitSound()
  CreateSound(#Sound, 1, 440)
  PlaySound(#Sound, #PB_Sound_Loop)
EndIf

Repeat
  Define Event = WaitWindowEvent()
Until Event = #PB_Event_CloseWindow

StopSound(#Sound)
If *WAVBuffer : FreeMemory(*WAVBuffer) : EndIf

Code: Select all


EnableExplicit

Enumeration 100
  #Window
  #WebView
EndEnumeration

OpenWindow(#Window, 100, 100, 400, 200, "Test", #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered)
WebViewGadget(#WebView, 0, 0, 400, 200)
SetGadgetItemText(#WebView, #PB_WebView_HtmlCode, ~"<script>ctx = new AudioContext(); osc = ctx.createOscillator(); osc.type = 'sine'; osc.frequency.value = 440; osc.connect(ctx.destination);</script>")
WebViewExecuteScript(#WebView, ~"osc.start(0);")

Repeat
  Define Event = WaitWindowEvent()
Until Event = #PB_Event_CloseWindow

User avatar
idle
Always Here
Always Here
Posts: 6168
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: PlaySound with a 20KHz file produces sound ?!

Post by idle »

escape75 wrote: Wed Jan 14, 2026 9:13 pm Ok that makes sense, although I've noticed that when you FreeMemory it just overwrites a few bytes at the beginning of the buffer
that's normal, the memory doesn't get zeroed it just get returned to the pool
Post Reply