Ooops
October 21, 2021, 16:39:16

Author Topic: DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer  (Read 3841 times)

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 363
    • Midimaster Music Education Software
FreeAudio RingBuffer SINGLE VALUE approach
« Reply #60 on: April 16, 2021, 07:37:30 »
FreeAudio RingBuffer SINGLE VALUE approach

I adapted the RingbufferClass to old BlitzMax 1.50 and added some security functions for audio timing to prevent buffer overrun.

Now we have again a common version for both BlitzMax NG and BlitzMax 1.50. See post #59 for the source code

In cooperation with Baggey we developed a vintage computer approach of sound speakers like they were used in the 80th. The FreeAudioRingBuffer with its SINGLE-VALUE approach is best for those retro machines:

ZX Spectrum Sound Emulator
Code: BlitzMax
  1. SuperStrict
  2. Import "RingBufferClass.bmx"
  3.  
  4. Graphics 800,600
  5.  
  6. RingBufferClass.SetDriver("FreeAudio DirectSound")
  7. Global RingBuffer:RingBufferClass = New RingBufferClass
  8. RingBuffer.CreateNew(50000, SF_MONO8)
  9.  
  10. Global A:Byte, B:Byte=1, C:Byte, D:Byte=0, E:Byte=0
  11. Global IY:Int
  12.  
  13. ' Music Data
  14. Global data%[] = [80,128,129,80,102,103,80,86,87,50,86,87,50,171,203,50,43,51,50,43,51,50,171,203,50,51,64,50,51,64,50,171,203,50,128,129,50,128,129,50,102,103,50,86,87,50,96,86,50,171,192,50,43,48,50,43,48,50,171,192,50,48,68,50,48,68,50,171,192,50,136,137,50,136,137,50,114,115,50,76,77,50,76,77,50,171,192,50,38,48,50,38,48,50,171,192,50,48,68,50,48,68,50,171,192,50,136,137,50,136,137,50,114,115,50,76,77,50,76,77,50,171,203,50,38,51,50,38,51,50,171,203,50,51,64,50,51,64,50,171,203,50,128,129,50,128,129,50,102,103,50,86,87,50,64,65,50,128,171,50,32,43,50,32,43,50,128,171,50,43,51,50,43,51,50,128,171,50,128,129,50,128,129,50,102,103,50,86,87,50,64,65,50,128,152,50,32,38,50,32,38,50,128,152,50,38,48,50,38,48,50,0,0,50,114,115,50,114,115,50,96,97,50,76,77,50,76,153,50,76,77,50,76,77,50,76,153,50,91,92,50,86,87,50,51,205,50,51,52,50,51,52,50,51,205,50,64,65,50,102,103,100,102,103,50,114,115,100,76,77,50,86,87,50,128,203,25,128,0,25,128,129,50,128,203,255]
  15. Print( "DATA LENGTH: "+Len(data) )
  16.  
  17. CALL_37596()
  18.  
  19. Repeat
  20.         Delay 5
  21. Until AppTerminate()
  22. End
  23.  
  24.  
  25.  
  26. Function CALL_37596:Int()
  27. ' simulation of an old Z80 machine code:
  28.         While True
  29.                 A = data[IY]
  30.                 If A=255
  31.                         Return True
  32.                 End If
  33.                 C = A  
  34.                 B = 0
  35.                 A = 0
  36.                 D = data[IY+1]
  37.                 E = data[IY+2]
  38.                 Repeat              
  39.                         Repeat                    
  40.                                 OUT_254(254,A)
  41.                                 D :- 1
  42.                                 If D=0
  43.                                         D = data[IY+1]
  44.                                         A = A~24  
  45.                                 End If
  46.                                 E :- 1
  47.                                 If E=0
  48.                                         E = data[IY+2]                        
  49.                                 End If
  50.                                 B :- 1                         
  51.                         Until B=0
  52.                         C :- 1                 
  53.                 Until C=0
  54.                 IY :+ 3
  55.                 Print "Step in DATA=" +  iy
  56.         Wend
  57. End Function
  58.  
  59.  
  60. Function OUT_254(Port%, Value%)
  61.         Select Value & 8
  62.                 Case 0
  63.                         RingBuffer.SendONE 50
  64.                 Case 8
  65.                         RingBuffer.SendONE 200
  66.         End Select
  67. End Function
  68.  

You will finde the RingBufferClass.bmx here in post #59:
https://www.syntaxbomb.com/worklogs/done!-a-new-audio-out-approach-in-blitzmax-freeaudio-ringbuffer/msg347049187/#msg347049187
« Last Edit: July 16, 2021, 07:59:15 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 363
    • Midimaster Music Education Software
Update BlitzMax FreeAudio RingBuffer
« Reply #61 on: July 15, 2021, 10:23:26 »
I wrote an update version 1.2 of the Ringbuffer. It now works on BlitzMax NG and BlitzMax 1.50.

Also I added a security feature to prevent buffer onverruns.

The third improvement is a SINGLE VALUE support, where you now can send single samples values instead of bulked datas too.

The last improvement: The User needs not to care about registering the needed threads any more. The library does this automatically when created.

So a minimalistic example now only needs a few code lines:
Code: BlitzMax
  1. SuperStrict
  2. Import "RingBufferClass.bmx"
  3. RingBufferClass.SetDriver("FreeAudio DirectSound")
  4. Global RingBuffer:RingBufferClass = New RingBufferClass
  5. RingBuffer.CreateNew(50000, SF_MONO8)
  6. ' thats all
  7.  
  8. 'now send sample values:
  9. Global Audio:TAudioSample = .....
  10. For local i%=0 to ...
  11.       RingBuffer.SendONE Audio.Samples[i]
  12. Next
  13.  

You will find the source code of the new version 1.2 in post #59:
https://www.syntaxbomb.com/worklogs/done!-a-new-audio-out-approach-in-blitzmax-freeaudio-ringbuffer/msg347049187/#msg347049187


You will find an example of SINGLE VALUE approach in post #60:
https://www.syntaxbomb.com/worklogs/done!-a-new-audio-out-approach-in-blitzmax-freeaudio-ringbuffer/msg347049552/#msg347049552


Feel free to ask questions or request for additional features here.



« Last Edit: July 16, 2021, 08:08:12 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline Baggey

  • Full Member
  • ***
  • Posts: 183
Re: Final Version of the FreeAudio-Ringbuffer
« Reply #62 on: July 24, 2021, 14:51:05 »
Here is the final version of an BlitzMax AudioDriver that works like a port. It is always open and you can throw single datas in it until the very last moment of 10msec before playing. If you have no datas, the AudioDriver keeps open, but silent.


Detailed description Version 1.2
The Driver is based on FreeAudio, so you have to choose "FreeAudio" or it's variants like "FreeAudio Directsound". Thanks to Derron, who coded the part of the direct access to the FreeAudio-Ram.

The RingBuffer needs Threaded Build by default. On old BlitzMax 1.50 you have to select this option manually.

You can select HERTZ as you want, but best result are with multiples of 1000, f.e. 22000 is better than 22050.

You can select FORMAT from 4 variants: SF_MONO8 or SF_STEREO8 or SF_MONO16LE or SF_STEREO16LE. This only affect the sending of your datas. Internally the AudioDriver converts it all to SF_STEREO16LE before sending it to the speakers.

If you send too few datas, you will not get any problems but PAUSE or little Breaks.
If you permanent send too many datas you will get a increasing latency until the buffer overruns*.

*)the new version 1.2 cares about buffer overrun by pausing your app for 20msec
each time the buffer threadens to overrun and reporting to the debug window:
"RINGBUFFER: Prevent Buffer Overrun! Wait for 20msec"


So you need to know what is the best amount of data.s.
Therefore you aks the functions RingBuffer.IntervalSamples() and RingBuffer.IntervalTime() for the best timing.

RULE: "Send  RingBuffer.IntervalSamples() each RingBuffer.IntervalTime()"

Method I: Sending a bulk of datas:
You have to control yourself the sending of your datas. Fill them in a local INT-Array and send this to the AudioDriver.
Code: BlitzMax
  1. RingBuffer.Send AudioArray
Your amount of sending can vary, but in total you should keep an eye on the optimal amount.



Method II: Sending single sample values:
Send single Sample Values this way to the AudioDriver:
Code: BlitzMax
  1. RingBuffer.SendOne Value
Your rate/intervall of sending can vary, but in total you should keep an eye on the optimal amount/msec.



The INT-Array or the SINGLE VALUE contains one sample value in one cell.
If you send 8bit-Samples the values need to be
  • unsignd-BYTE
If you send 16bit-Samples it does not matter whether this value is...
  • unsigned-SHORT
  • signed-SHORT
  • signed-INTEGER


So here is the Class: RingBufferClass.bmx for BlitzMax NG and BlitzMax 1.50
Code: BlitzMax
  1. SuperStrict
  2. Type RingBufferClass
  3.         ' *** RingBufferClass.bmx  ***
  4.         ' A permant running Audio-Output using FreeAudio
  5.         ' --------------------------------------------------
  6.         ' Author: Midimaster at www.midimaster.com
  7.         ' V1.2 2021-07-15
  8.         ' see examples of use at https://www.syntaxbomb.com/index.php/topic,8377.0.html
  9.         ' -------------------------------------------------------------------
  10.         ' minimal example:
  11.         '   Import "RingBufferClassFinal.bmx"
  12.         '   RingBufferClass.SetDriver("FreeAudio....")
  13.         '   Global RingBuffer:RingBufferClass = New RingBufferClass
  14.         '   RingBuffer.CreateNew(HERTZ, FORMAT)
  15.         '   SendSize:INT  = RingBuffer.IntervalSamples()
  16.         '   SendTime:INT = RingBuffer.IntervalTime()
  17.         '   Global WriteTime = MilliSecs()
  18.         '   Function SendSamples
  19.         '               If WriteTime>MilliSecs() Return
  20.         '               WriteTime =WriteTime + SendTime
  21.         '               Local AudioArray:Int[SendSize]
  22.         '               For Local i:Int=0 To SendSize-1
  23.         '                       AudioArray[i] = any value....
  24.         '               Next
  25.         '               RingBuffer.Send AudioArray
  26.         '   End Function
  27.  
  28.         Global MyDriver$
  29.         Global BufferMutex:TMutex=CreateMutex()
  30.  
  31.         Field CHANNELS:Int, CHUNK_SIZE:Int, BUFFER_SAMPLES:Int, BUFFER_SIZE:Int
  32.         Field FORMAT:Int, CHUNK_TIME:Int, HERTZ:Int, BITS:Int
  33.         Field WritePointer:Int, ReadPointer:Int, RingPointer:Int
  34.         Field WriteTime:Int, WatchTime:Int, InFormat:Int
  35.  
  36.         Field RingBuffer:TAudioSample, InBuffer:TAudioSample, Sound:TSound
  37.         Field RingStream:TStream, InBufferStream:TStream
  38.         Field WatchThreadB:TThread
  39.        
  40.  
  41.         Function SetDriver(Driver$)
  42.         ' PUBLIC: Use this to...
  43.                 ' select one of the audio drivers. It needs to be FreeAudio
  44.                 ' on Windows and BlitzMax 1.5 needs to be FreeAudio DirectSound
  45.                 If MyDriver<>"" Return
  46.                 If Driver.contains("FreeAudio")=False
  47.                         Notify "wrong AudioDriver"
  48.                         End
  49.                 EndIf
  50.                 MyDriver = Driver
  51.                 SetAudioDriver(MyDriver)
  52.         End Function
  53.  
  54.        
  55.        
  56.         Method CreateNew(Hertz:Int, UserFormat:Int=SF_STEREO16LE , Latency:Int=8)
  57.         ' PUBLIC: Use this to define the Ringbuffer...
  58.                         ' HERTZ should be a multiple of 1000 for CHUNK_TIME=10, 20, 40 or 50
  59.                         ' HERTZ can also be 44100 when CHUNK_TIME=20 or 40
  60.                         '
  61.                         ' UserFormat can be SF_MONO8 or SF_STEREO8 or SF_MONO16LE or SF_STEREO16LE
  62.                         '
  63.                         ' LATENCY can be from 1 to 32
  64.                         ' 2=extrem small, 4=normal size,  8-32..=secure sizes
  65.                         '      
  66.                         If MyDriver=""
  67.                                         Notify "No AudioDriver selected"
  68.                                         End
  69.                         EndIf
  70.                         Self.HERTZ=Hertz
  71.                         Self.FORMAT=SF_STEREO16LE
  72.                         Self.InFormat=UserFormat
  73.                         DefineBuffer Latency
  74.                         ClearBuffer
  75.                         WatchTime=MilliSecs()
  76.                         PlaySound Sound
  77.                         Local WatchThread:TThread = CreateThread(WatchLoop,Self)
  78.         End Method
  79.  
  80.  
  81.  
  82.         Method IntervalSamples:Int()   
  83.         ' PUBLIC: Use this to...
  84.                 ' inform how many samples you should send each call    
  85.                 If  (InFormat=SF_MONO8) Or (InFormat=SF_MONO16LE)
  86.                         Return CHUNK_SIZE/4
  87.                 Else
  88.                         Return CHUNK_SIZE/2
  89.                 EndIf
  90.         End Method
  91.  
  92.        
  93.         Method IntervalTime:Int()
  94.         ' PUBLIC: Use this to...
  95.                 ' inform how long you should wait between calls (in msecs)     
  96.                 Return CHUNK_TIME
  97.         End Method      
  98.  
  99.  
  100.  
  101.  
  102.        
  103.         Method SendOne(Value:Int)
  104.         ' PUBLIC: Use this to...
  105.                 ' send one single sample value to the ringbuffer
  106.                 Global ShortArray:Short[IntervalSamples()*2]
  107.             Global Counter:Int
  108.  
  109.                 Select InFormat
  110.                         Case SF_MONO8                                    
  111.                                 Value=(Value-128)*128
  112.                                 ShortArray[Counter]   = Value
  113.                                 ShortArray[Counter+1] = Value
  114.                                 Counter=Counter+2
  115.                         Case SF_STEREO8                                          
  116.                                 Value=(Value-128)*128
  117.                                 ShortArray[Counter] = Value
  118.                                 Counter=Counter+1
  119.                         Case SF_MONO16LE
  120.                                 ShortArray[Counter]   = Value
  121.                                 ShortArray[Counter+1] = Value
  122.                                 Counter=Counter+2
  123.                         Case SF_STEREO16LE
  124.                                 ShortArray[Counter] = Value
  125.                                 Counter=Counter+1
  126.                 End Select
  127.                 If Counter=IntervalSamples()*2
  128.                         CheckBufferOverRun
  129.                         Transfer ShortArray
  130.                 Counter=0
  131.                 EndIf
  132.         End Method
  133.  
  134.  
  135.  
  136.         Method Send(AudioArray:Int[])
  137.         ' PUBLIC:  Use this to...
  138.                 ' send a couple of samples value to the ringbuffer
  139.                 Local ShortArray:Short[]
  140.                 Select InFormat
  141.                         Case SF_MONO8                                    
  142.                                         ShortArray= New Short[AudioArray.Length*2]
  143.                                         For Local i:Int=0 To AudioArray.Length-1
  144.                                                 Local v:Int = (AudioArray[i]-128)*128
  145.                                                 ShortArray[2*i]   = v
  146.                                                 ShortArray[2*i+1] = v
  147.                                         Next
  148.                         Case SF_STEREO8
  149.                                         ShortArray= New Short[AudioArray.Length]
  150.                                         For Local i:Int=0 To AudioArray.Length-1
  151.                                                 ShortArray[i] = (AudioArray[i]-128)*128
  152.                                         Next
  153.                         Case SF_MONO16LE
  154.                                         ShortArray= New Short[AudioArray.Length*2]
  155.                                         For Local i:Int=0 To AudioArray.Length-1
  156.                                                 ShortArray[2*i]   = AudioArray[i]
  157.                                                 ShortArray[2*i+1] = AudioArray[i]
  158.                                         Next
  159.                         Case SF_STEREO16LE
  160.                                          ShortArray= New Short[AudioArray.Length]
  161.                                         For Local i:Int=0 To AudioArray.Length-1
  162.                                                 ShortArray[i] = AudioArray[i]
  163.                                         Next
  164.                 End Select
  165.                 CheckBufferOverRun
  166.                 Transfer ShortArray
  167.         End Method     
  168.  
  169.  
  170. '
  171. '   E N D   O F   T H E   P U B L I C   F U N C T I O N S
  172. '
  173. ' ***************************************************************************
  174. '
  175. '    I N T E R N A L   F U N C T I O N S :
  176. ?bmxng
  177. Private
  178. ?
  179.          Method Watch()
  180.         ' PUBLIC: Use this to...
  181.                 ' inform how many samples you should send each call    
  182.                 If WatchTime<MilliSecs()
  183.                         WatchTime = WatchTime + CHUNK_TIME
  184.                         SendOneChunk
  185.                 EndIf
  186.         End Method
  187.  
  188.  
  189.  
  190.         Function WatchLoop:Object(data:Object)
  191.         Local RingBuffer:RingBufferClass = RingBufferClass(data)
  192.         If Not RingBuffer Then Return Null  
  193.                 Repeat
  194.                         Delay 3
  195.                         RingBuffer.Watch
  196.                 Forever
  197.         End Function
  198.        
  199.                
  200.         Method DefineBuffer(Latency:Int)
  201.                         CHUNK_TIME=20
  202.                         BITS=16
  203.                         CHANNELS=2
  204.                         CHUNK_SIZE     = HERTZ * CHUNK_TIME * CHANNELS * BITS/8/1000
  205.                         BUFFER_SAMPLES = 4*Latency * HERTZ * CHUNK_TIME             /1000
  206.                         BUFFER_SIZE    = BUFFER_SAMPLES * BITS/8 *CHANNELS
  207.                        
  208.                         RingBuffer     = CreateAudioSample(BUFFER_SAMPLES, HERTZ, FORMAT)
  209.                         InBuffer       = CreateAudioSample(BUFFER_SAMPLES, HERTZ, FORMAT)
  210.  
  211. ?bmxng
  212.                         Local fa_sound:Byte Ptr  = fa_CreateSound( BUFFER_SAMPLES-1, BITS, CHANNELS, HERTZ, RingBuffer.Samples, $80000000 )
  213. ?Not bmxng
  214.                         Local fa_sound:Int       = fa_CreateSound( BUFFER_SAMPLES-1, BITS, CHANNELS, HERTZ, RingBuffer.Samples, $80000000 )
  215. ?
  216.  
  217.                         Sound          = TFreeAudioSound.CreateWithSound( fa_sound, Null)
  218.                         RingStream     = CreateRamStream(RingBuffer.Samples , Buffer_size,True,True)
  219.                         InBufferStream = CreateRamStream(InBuffer.Samples,Buffer_size,True,True)
  220.                         RingPointer    =  BUFFER_SIZE/4
  221.         End Method
  222.  
  223.  
  224.          Method SendOneChunk()
  225.                 LockMutex BufferMutex
  226.                         Local ReadPointerMod:Int = ReadPointer Mod BUFFER_SIZE
  227.                         InBufferStream.Seek ReadPointerMod
  228.                         RingStream.Seek RingPointer
  229.                         If ReadPointer + CHUNK_SIZE > WritePointer
  230.                                 Local Maxi:Int=WritePointer-ReadPointer
  231.                                 For Local i:Int = 0 To Maxi-1
  232.                                                 RingStream.WriteByte InBufferStream.ReadByte()
  233.                                 Next
  234.                                 For Local i:Int = Maxi To CHUNK_SIZE-1
  235.                                                 RingStream.WriteByte 0
  236.                                 Next
  237.                                 ReadPointer=ReadPointer + Maxi
  238.                         Else
  239.                                 For Local i:Int = 0 To CHUNK_SIZE-1
  240.                                         RingStream.WriteByte InBufferStream.ReadByte()
  241.                                 Next
  242.                                 ReadPointer=ReadPointer + CHUNK_SIZE
  243.                         EndIf
  244.                         RingPointer=(RingPointer + CHUNK_SIZE) Mod (BUFFER_SIZE)
  245.                 UnlockMutex BufferMutex
  246.         End Method
  247.  
  248.  
  249.          Method ClearBuffer()
  250.                         For Local i:Int =0 To BUFFER_SIZE
  251.                                 RingBuffer.Samples[i]=0
  252.                         Next   
  253.         End Method
  254.        
  255.        
  256.          Method Transfer(ShortArray:Short[])
  257.                 LockMutex BufferMutex
  258.                 Local WritePointerMod:Int = WritePointer Mod BUFFER_SIZE
  259.                 InBufferStream.Seek WritePointerMod
  260.                 If ShortArray.Length*2 + (WritePointerMod) <= BUFFER_SIZE
  261.                         For Local i:Int=0 To ShortArray.Length-1
  262.                                 InBufferStream.WriteShort ShortArray[i]
  263.                         Next                   
  264.                 Else
  265.                         Local Maxi:Int = BUFFER_SIZE-WritePointerMod
  266.                        
  267.                         For Local i:Int=0 To Maxi/2-1
  268.                                 InBufferStream.WriteShort ShortArray[i]
  269.                         Next
  270.                         InBufferStream.Seek 0
  271.                         For Local i:Int=Maxi/2 To ShortArray.Length-1
  272.                                 InBufferStream.WriteShort ShortArray[i]
  273.                         Next
  274.                 EndIf
  275.                 WritePointer=WritePointer + ShortArray.Length*2
  276.  
  277.                 UnlockMutex BufferMutex
  278.         End Method
  279.        
  280.        
  281.         Method CheckBufferOverRun()
  282.         ' private  cares about buffer overruns and report if you send to fast
  283.                 Local grade:Int
  284.                         Local diff:Int=WritePointer-Readpointer
  285.                         diff=diff*100/BUFFER_SIZE
  286.                        
  287.                         If diff>80
  288.                                 Print "RINGBUFFER: Prevent Buffer Overrun! Wait for " + IntervalTime() + "msec"
  289.  
  290.                                 Delay IntervalTime()
  291.                                 'Print WritePointer + " " + Readpointer + " " + (WritePointer-Readpointer) + " " + BUFFER_SIZE
  292.                                 'Print diff + ":Int"
  293.                         EndIf
  294.         End Method
  295. End Type
  296.  


Some examples will follow...

Code Example "Some Noise":
Code: BlitzMax
  1. SuperStrict
  2. Import "RingBufferClass.bmx"
  3. Graphics 800,600
  4. RingBufferClass.SetDriver("FreeAudio DirectSound")
  5. Global RingBuffer:RingBufferClass = New RingBufferClass
  6. RingBuffer.CreateNew(12000, SF_MONO16LE)
  7.  
  8. Global SendSize:Int  = RingBuffer.IntervalSamples()
  9. Global SendTime:Int = RingBuffer.IntervalTime()
  10.  
  11. Global WriteTime:Int = MilliSecs()
  12. Repeat
  13.     Cls
  14.     SendSamples
  15.     Flip
  16. Until AppTerminate()
  17.  
  18. Function SendSamples()
  19.             If WriteTime>MilliSecs() Return
  20.             WriteTime =WriteTime + SendTime
  21.             Local AudioArray:Int[SendSize]
  22.             For Local i:Int=0 To SendSize-1
  23.                     AudioArray[i] = Rand(-1000,+1000)
  24.             Next
  25.             RingBuffer.Send AudioArray
  26. End Function
  27.  



Code Example "Stereo Sinus Mouse":
Code: BlitzMax
  1.  
  2. SuperStrict
  3. Import "RingBufferClass.bmx"
  4. Graphics 800,600
  5. RingBufferClass.SetDriver("FreeAudio DirectSound")
  6. Global RingBuffer:RingBufferClass = New RingBufferClass
  7. RingBuffer.CreateNew(48000, SF_STEREO16LE)
  8.  
  9. Global SendSize:Int  = RingBuffer.IntervalSamples()
  10. Global SendTime:Int = RingBuffer.IntervalTime()
  11.  
  12. Global WriteTime:Int = MilliSecs()
  13. Repeat
  14.     Cls
  15.         DrawText "Move your Mouse in X-dir to change the LEFT speaker", 100,100
  16.         DrawText "Move your Mouse in Y-dir to change the RIGHT speaker", 100,130
  17.     SendSamples
  18.     Flip
  19. Until AppTerminate()
  20.  
  21.  
  22. Global Arc1:Double,  Arc2:Double
  23.  
  24. Function SendSamples()
  25.         If WriteTime>MilliSecs() Return
  26.         WriteTime =WriteTime + SendTime
  27.         Local AudioArray:Int[SendSize]
  28.         For Local i:Int=0 To SendSize-1 Step 2
  29.                 arc1=arc1+MouseX()/100.0+1
  30.                 AudioArray[i] = Sin(arc1)*10000.0
  31.                
  32.                 arc2=arc2+MouseY()/100.0+1
  33.                 AudioArray[i+1] = Sin(arc2)*10000.0
  34.         Next
  35.         RingBuffer.Send AudioArray
  36. End Function
  37.  



Code Example "Moving free in an Audio-File":
(you need the file TextABC.ogg from attachment)
Code: BlitzMax
  1.  
  2. SuperStrict
  3. Import "RingBufferClass.bmx"
  4. Graphics 800,600
  5. RingBufferClass.SetDriver("FreeAudio")
  6. Global RingBuffer:RingBufferClass = New RingBufferClass
  7.  
  8. Global Source:TAudioSample=LoadAudioSample("TestABC.ogg")
  9.  
  10. RingBuffer.CreateNew(12000, SF_MONO16LE)
  11.  
  12. Global SendSize:Int  = RingBuffer.IntervalSamples()
  13. Global SendTime:Int = RingBuffer.IntervalTime()
  14.  
  15. Global WriteTime:Int = MilliSecs()
  16.  
  17. Global Pointer:Int ,LastMouse:Int, Speed:Int=1
  18.  
  19. Repeat
  20.     Cls
  21.         SetColor 255,255,255
  22.         DrawText "Mouse the Slider to navigate through the spoken ABC", 100,100
  23.         DrawRect 50,300,700,20
  24.         SetColor 1,1,1
  25.         DrawRect 52,302,696,16
  26.         SetColor 255,255,255
  27.         DrawOval 50+ Pointer*680/Source.Length/2,298,25,25     
  28.         SetColor 1,1,1
  29.         DrawOval 52+ Pointer*680/Source.Length/2,300,21,21     
  30.    SendSamples
  31.         If MouseDown(1)
  32.                 If MouseX()>49 And MouseX()<745
  33.                         If LastMouse<>MouseX()
  34.                                 LastMouse = MouseX()
  35.                                 Local p%=(MouseX()-50)*Source.Length*2/700
  36.                                 Pointer= p & $FFFFF4
  37.                                 Speed=2
  38.                         Else
  39.                                 Speed=1
  40.                         EndIf
  41.                 EndIf
  42.         Else
  43.                 Speed=1
  44.                 LastMouse=0
  45.         EndIf
  46.     Flip
  47. Until AppTerminate()
  48.  
  49.  
  50.  
  51. Function SendSamples()
  52.         If WriteTime>MilliSecs() Return
  53.         WriteTime =WriteTime + SendTime
  54.         Local AudioArray:Int[SendSize]
  55.         For Local i:Int=0 To SendSize-1
  56.                 Local V%= Source.Samples[Pointer+2*i*Speed]+Source.Samples[Pointer+2*i*Speed+1]*256
  57.                 AudioArray[i] = Source.Samples[Pointer+2*i*Speed]+Source.Samples[Pointer+2*i*Speed+1]*256
  58.         Next
  59.         RingBuffer.Send AudioArray
  60.         Pointer=(Pointer + 2*SendSize*Speed) Mod ((Source.Length*2)-2*SendSize)
  61. End Function
  62.  

more code sample will follow... see next post!

Trying the 'Moving Free in an Audio-File Code' im getting noise at the (start or at the end) or possible everytime the audio selector moves or is up dated on my system.
Moving slider quickly "I hear the Minions"  :)) I shall be reading Further.

Baggey
Currently Running a PC that just Aint fast enough!?
ZX Spectrum 48k, NEXT, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip.

Jesus was only famous because of his DAD.

Offline Baggey

  • Full Member
  • ***
  • Posts: 183
Re: DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #63 on: July 24, 2021, 15:50:27 »
if We still recive this message are we losing sound DATA? I assume we haved filled the buffer were waiting for it to playout so we can add data's again.

How did you add the 3D effect of RED to the post?

Okay just got this one we use I'm just glowing
USE  {glow=red,2,300}I'm just glowing{/glow} Change sqigly brackets for square brackets  :D

Quote
*)the new version 1.2 cares about buffer overrun by pausing your app for 20msec
each time the buffer threadens to overrun and reporting to the debug window:
"RINGBUFFER: Prevent Buffer Overrun! Wait for 20msec"

Quote
Feel free to ask questions or request for additional features here.

How do we apply a control over volume in the RingBuffer? A Fluke of the ZX Spectrum is if you turn the Speaker bit on and the Mic bit on at the same time the Electonic circuit back feeds and amplifies the sound very Slightly! Could this be achieved  :-\

Baggey
« Last Edit: July 24, 2021, 18:50:46 by Baggey »
Currently Running a PC that just Aint fast enough!?
ZX Spectrum 48k, NEXT, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip.

Jesus was only famous because of his DAD.

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 363
    • Midimaster Music Education Software
Re: DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #64 on: July 25, 2021, 09:41:40 »
if We still recive this message are we losing sound DATA? I assume we haved filled the buffer were waiting for it to playout so we can add data's again.

This new message...
"RINGBUFFER: Prevent Buffer Overrun! Wait for 20msec"

...of the ringbuffer does not lose datas! This message only will inform you, that you tried to overfill the buffer and now the buffer reacts with a little break of 20msec with the purpose not to lose your datas. It is only a information in DEBUG mode, that the OP should control his timing in filling the buffer.

In case of overruns a ringbuffer can follow two strategies:

1.
Inform the user that the buffer cannot process the datas and now the user has to care about waiting for a later moment to repeat the sending of this packet.

2.
Waiting automatic for a little time until the buffer can receice the sended packet. This frees the user of thinking about timing, but blocks the computer for 20msec.

The second is the strategy of my ringbuffer. But the user should take the message for serious. In a perfect final app, a buffer overrun should not happen anyway.


Quote
How do we apply a control over volume in the RingBuffer? A Fluke of the ZX Spectrum is if you turn the Speaker bit on and the Mic bit on at the same time the Electonic circuit back feeds and amplifies the sound very Slightly! Could this be achieved

At the moment the FreeAudio-Ringbuffer works like a "at-the-very-last-moment"-audioport. It does not process any datas, but sending them with low latency to the speakers. Thats also the reason, why the buffer is so small. It only has buffer for 20msec of datas.

You could add functions into the methods Send() or SendOne() by yourself. But you can also manipulate the volume in the datas, before sending them. In your Z80-player you use the fix values 50 and 200 to simulate a SQUARE-Wave. Because you use SF_MONO8 the volume would work like this:

Code: [Select]
128 and 128 means 0 volume
127 and 129 means volume 1
118 and 138 means volume 10
 28 and  228 means volume 100
the maximum combination is ...
  0 and 255 means volume 127
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline iWasAdam

  • Hero Member
  • *****
  • Posts: 2478
Re: DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #65 on: July 25, 2021, 10:44:16 »
Puts on swimsuit and dive in...
Quote
...of the ringbuffer does not lose datas
data is the plural of datum
Datas is a Brazilian municipality in the north-center of the state of Minas Gerais

as a professional you know this?

Quote
128 and 128 means 0 volume
127 and 129 means volume 1
118 and 138 means volume 10
 28 and  228 means volume 100
the maximum combination is ...
  0 and 255 means volume 127

Now without being snarky... WTF!

This has got to be some of the crappiest code/explanation I have EVER seen... period. And I've see a lot...

let's assume that the explanation is correct and that your nonsensical volume revolves around 128 being 0.
Surely it would help everyone (including yourself) to write a tiny method to wrap all the crap up into something like:

SetVolume( volume:int ) ' where volume is 0=no volume to 127 full volume

Thirdly. The ring buffer
evidently it complain if you send it too little data and also if you send it too much data... So it pauses while you get to figure out what the heck it's doing now? <- that's just terrible programming.

Your ring buffer should poll back (a callback) to your app that it wants data of a certain kind and size. if it doesn't get it then it should be able to handle it.

i am certainly not having a go at your programming - but the two users who are attempting to use your code are constantly having issues. which need detailed explanation of the strangest kind (see volume above)


Ok. Here's a simple lesson for you:
if your users are having issues - it is NOT their fault. It is your fault for
a. not explaining clearly what your code needs
b. the code itself is so poorly written it can't be understood

There seems to be some form of underlying additional complexity going on.
The ring buffer itself should be simple taking up less that 50 lines of code...

I went back to look over my own code (because it works flawlessly I never need to check it)
Here is the header for the ring buffer
Code: [Select]
Namespace audio

#Import "ringbuffer.h"

Extern

Class RingBuffer Extends Void
Field readPointer:Int
Field writePointer:Int
Method Handle:Void Ptr()
Method WriteSamples( samples:Double Ptr, sampleCount:Int )
Function Callback( a:Void Ptr, b:UByte Ptr, byteCount:Int )
Function Create:RingBuffer()
End

and here's the .h file itself
Code: [Select]
#pragma once
#include <deque>
#include <mutex>
#include <algorithm>

typedef double Sample;

class RingBuffer{

std::mutex mutex;
std::deque<Sample> buffer;

public:
int readPointer = 0;
int writePointer = 0;


void WriteSamples(Sample *samples, int count){
mutex.lock();
for(int i=0; i<count; i++){
buffer.push_back( samples[i] );
}
writePointer += count;
mutex.unlock();
}


void readSamples(short *dest, int sampleCount){
mutex.lock();
int available = buffer.size();
if (available >= sampleCount){
for(int i=0; i<sampleCount; i++){
Sample s = buffer.front();
buffer.pop_front();
dest[i] = 32767*std::max(-1.0, std::min(s, 1.0));
}
readPointer += sampleCount;
}
mutex.unlock();
}

static void Callback(void *a, unsigned char *b, int c){
memset(b, 0, c);
auto pipe = (RingBuffer*)a;
int sampleCount = c/2;
short *dest = (short *)b;
pipe->readSamples(dest, sampleCount);
}

void *Handle(){
return (void *)this;
}

static RingBuffer *Create(){
return new RingBuffer();
}
};

Dont even attempt to compile it - you wont be able to.

What it shows that this stuff is simple. you set it up and it works - the rest you build on top of this.

Getting back to the volume crapology
Code: [Select]
method SetVolume( inVolume:int )
newVolume = inVolume + 128
end method
isn't that a better solution? adding a clamp or bounds checks would be even better. but it's a simple thing that would stop your users being confused...

Offline iWasAdam

  • Hero Member
  • *****
  • Posts: 2478
Re: DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #66 on: July 25, 2021, 10:57:09 »
oh I missed this one- its a classic:  :o
Quote
but sending them with low latency to the speakers

NO, no BIG NO, and NO again.

At no stage do you send ANYTHING to the speakers - you app does not even know anything about speakers. it knows about audio drivers - they handle the internal transfer of audio data to whatever output is defined. and this can be redefined outside of your app at any time. so in theory and app which looses driver output for a given output can crash - but that is another story.

In essence you app says - hey driver what have you got connected?
the driver responds with an amount of attatched devices - usually starting from 0. although minus numbers may be fed back to the driver for the default device.
you then queery the driver with the device number, and the driver will give you a string representation of the device
You usually attach speakers to the 'Line Out' - sometimes depending on your system you might even have internal speakers

Here's a quick list of the attatched sound devices on my system


depending on the audio driver I use, some of these might dissapear.

One other thing to be really aware of - each time you run your app, it is possible that these 'devices' will be in different positions and have different numbers - it's you who must keep track and play nice with them.

But NO... You never send anything to the speakers - you send data to the audio driver and it handles all of that...
« Last Edit: July 25, 2021, 11:00:12 by iWasAdam »

Offline iWasAdam

  • Hero Member
  • *****
  • Posts: 2478
Re: DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #67 on: July 25, 2021, 11:28:20 »
audio programming is low level - you must be precise with your explanations.

your app speaks to the audio driver - the driver is the interface between the hardware and the system/you
the driver is set up to speak to an attached device - this is at a hardware level

How you deal with the driver is up to you - but in essence. it is driver:GIVE ME DATA... you: here you go...
if the data is wrong or missing the driver will either get upset and crash or just make stuff up (noise)
The driver are not too intelligent and wont care about your data - that is your responsibility.

There is no one simple way that works - this is very low level stuff and gets nasty very quickly.

I find that the best way to deal with it is to try and get the core working in whatever way is the tightest and works.
Then build a simple interface on top of this. on top of that you build you audio systems and your user interfaces, etc.

I have found that a good ring buffer is the best way to deal with things.
The ring buffer callback when it wants feeding and also has a forward buffer so it is never hungry

How do I set up the ring buffer:
Code: [Select]
const FRAGMENT_SIZE:double = 512 '<- a single channel size
const FRAGMENT_SIZE2:double = FRAGMENT_SIZE * 2 '<- x2 because we are dealing with stereo
const WRITE_AHEAD:double = FRAGMENT_SIZE * 6 '<- the forward buffer is always kept x3 fed with data
' const FREQUENCY:double = 48000 '<- true fairlight frequency - actually it is really 96000 and then divided to get the correct frequencies (engineer told me how this works)
const FREQUENCY:double = 44100 '<- base frequency to keep consistency with previous versions


in a thread a have an updateaudio to keep the buffers filled
Code: [Select]
Method UpdateAudio()
_runTime += 1
Local buffered:long
Local buffer:double[]
While True
buffered = ringBuffer.writePointer - ringBuffer.readPointer
If buffered >= WRITE_AHEAD Exit
buffer = FillAudioBuffer( FRAGMENT_SIZE )
ringBuffer.WriteSamples( Varptr buffer[0], FRAGMENT_SIZE2 )
Wend

The one that you can see is the buffers and audio data is stored in memory blocks of doubles (2x for stereo)

doubles give you HUGE amount of control over the output audio. it's got amazing precision, and you can doo all sorts of very quick but simple stuff like realtime volume, etc.

How do I deal with audio files?
A. Simple
1. you convert EVERYTHING into a simple single format when you import the audio (and do the same when you write the audio back to a file)
2. the core internal sound format is doubles or floats (you can use either but use only one) with the general volume profile being -1 to 1

so reading out data to the buffer with a volume would be something line
Code: [Select]
volume:float = 0 .. 1
begin
leftbuffer = leftsample * volume
rightbuffer = rightsample * volume
next buffer position
next sample position
end

Now I know there have been issues with playhead (this is the current position in the current sample)
Lets take the code above
Code: [Select]
volume:float = 0 .. 1
sndPos = start of sample
_playhead:long '<- links to an external variable we can look at to get the current position of the playhead
begin
leftbuffer = leftsample[sndPos] * volume
rightbuffer = rightsample[sndPos] * volume

_playhead = (sndPos Mod sample.Length)

next buffer position
sndPos += next sample position offset
end


Now I'm not saying that this code is good or correct. but it is simple. it is easy to read even for someone who has very little experience with audio.
It is also very small and tight and therefore FAST....

Offline iWasAdam

  • Hero Member
  • *****
  • Posts: 2478
Re: DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #68 on: July 25, 2021, 12:00:29 »
I've got to add 'just one more thing'....  :o

T I M I NG....

Your machine is faster than mine is, and my machine is faster than Qubes, etc, etc

The buffer system should be running at a single defined speed (usually 44100) there is really no reason to use different speeds, etc.

But... You have to deal with timing systems yourself  - usually by interrogating the system timer Millisecs (1000 ticks per second) or (if I recall correctly microsecs which is 1000000 per second - i'd need to check that one)

So (lets assume it was for a spectrum emulator), you would need to have some mechanism that corrects for the computer speed and system speed giving a stable result - it might be that you build a z80 system running at a fixed speed and use that to feed the buffers?

In my own experience I deal with high precision audio synthesis, with correct note output being what I watch closely...

I have static sample buffers and read from these to the audio buffers in realtime modifying the output as needed.

Although I have direct control into the audio systems, I don't run these at buffer sped but generally at around 60-70 times a second - that is enough for giving time to the system and not overloading it.

UI stuff is handled completely separate from the audio systems. It can read and modify, display the sample data etc. but playback is all being done without a gui.
« Last Edit: July 25, 2021, 12:03:52 by iWasAdam »

Offline iWasAdam

  • Hero Member
  • *****
  • Posts: 2478
Re: DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #69 on: July 25, 2021, 14:17:37 »
OK guys we have a personal response. so I will make sure everyone else see this:
Quote
I dont know, what you want, but you cannot ignore the fact that the Audio-format SF_MONO8 is defined as:

Zero-Level (Silence) is 128
values below 127 are equivalent to negative values in a 32bit-float-approach
values above 129 are equivalent to positiv values in a 32bit-float-approach

Value 0 is equivalent to -1 a 32bit-float-approach
Value 255 is equivalent to +1 a 32bit-float-approach

If Baggey is simulating a 8bit machine he only has BYTES!!!! and he has to handle with it. So if he wants to create a SQUARE WAVE of full volume he has to use the 0 and the 255. And if he wants to use the half volume he has to use the 64 and 196.

By the way... You are extremly arrogant. I know of course that the Ringbuffer does not communicate directly to the speaker. AND YOU KNOW TOO, THAT I OF COURSE KNOW THIS. I wrote this only because of the context of a Z80, where the port256 perhaps was really the last step towards the speaker. So Baggey can see the buffer as the speaker itself.

And thank you for your explanation of DATAS. This helped others a lot in understanding my text. Please continue correcting my english. It looks you have a lot of time...

Now if I was to respond It would be something along the lines of before: E.G

1. it is irrelevant how you deal with emulating something - it's the end result that count - so you could use stereo at 128bits to emulate an 8 bit system. the best approach is to take a nice simple and easy way. using signed 8bits can be a complex way to deal with things. it would be better to take a step back look at the system and find a nice simple way to do it

2. the above volume code is absolute pants! you cannot in anyway justify it

3. This person 'knows' what is going on and knows the code is terrible The quote "I know of course that the Ringbuffer does not communicate directly to the speaker" Brilliant - then why did you say otherwise. if you are so wise, what the bugger are you doing telling people to do this do that (deliberately knowing that what you are saying is wrong)

4."By the way... You are extremly arrogant.". But at least I am trying to tell people how things work instead of telling them utter rubbish in the hope that they understand what is going on - even though what you tell them (you deliberately know) is wrong.

Just remember it was you who said they didn't understand c++, didn't understand this and that. and kept complaining... And then you decide that you are the font of all knowledge relating to audio stuff. You then decide that you now know everything (and tell people to do what you say and tell them deliberately incorrect information). And then complain when others have serious issues with you code because it's (I think the word is) Arcane. <- something that is old, complex and preferably secret...

It should also be again repeated - Audio is low level, it is complex and nasty. the best way with it is to be clear and simple. I've at least tried to give a different approach with this in mind - You on the other hand want to create the most complex solution to a simple problem. feed inaccurate information and then sit on your (obviously) gilded throne and relish when people can't figure out what the hell you mean. mysterious variables that appear out of nowhere, volume that starts at 128 and can go in both directions with the same result.

It also seems that the only time you actually begin to take the time and try to explain something with a little more clarity is when you are provoked to do so.

Maybe you should take some time and look at what is being said...

I will end with another direct quote from you:
Quote
I know of course that the Ringbuffer does not communicate directly to the speaker.
Could you please explain to everyone why you said it did?

Offline iWasAdam

  • Hero Member
  • *****
  • Posts: 2478
Re: DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #70 on: July 25, 2021, 14:51:36 »
Do you know what folks - I just went back and reread what I and other said and thought I would bring this up:
Quote
128 and 128 means 0 volume
127 and 129 means volume 1

Now.... (wheres my swimsuit...)
lets take this apart 128 = no volume and 129 = volume 1
That's not actually correct - infact it's completely incorrect.

If we feed ANY static 8 bit value we will get silence. silence = no sound. so feeding any repeating single value will be silent!
So that means that any value is 0 volume!!!!

OK i'm being pedantic and deliberately snarky here - but the fact remains that this is correct.

Why?
A. because it is the relationship between the values that causes the sound - any single repeating number will be silence!

Let's assume we are feeding a repeating square wave - we could have the 2 values being 0 and 255. but we also need to know if the DRIVER is set up to accept signed or unsigned values.

if set up to accept signed values then we can only send -127 to 128 with 0 being the center point
if unsigned then values from 0 to 255 are acceptable
byte vs ubyte

Lets (just for the same of an example) accept we are dealing with unsigned values (a common 8bit audio data format)

we can send 0 and 128 and get half volume, 0 and 64 and get quarter volume 0 and 255 and get full volume
Why?

Because It's not the value but the difference between the values that counts. This is a fundamental truth.

You could send 0 and 128, or 128 and 255, or 64 and 191 - the audio result would be exactly the same - it is the difference between the values that counts.

If you really want to be pedantic it is the relationship between voltages and the movement of the speakers back and forth that creates the sound.

But it is much simpler to think about audio with 0 being the center point. That way you can visualise sin waves going from -1 to 1 around 0.

And in that same breath - it is much simpler to think of volume being from 0 to a high number (1 is a good number) because that is the way we (as humans) think of volume 0 = no volume, 1 or 100 being full volume.

Why would 1 be a good number to have a the high volume?
A.
- Lets assume you have a +- value. Let's say 5.
- if we multiply 5 x 1 = 5
- if we multiply 5 x 2 = 10
- if we multiply 5 x 0.5 = 2.5

nice and simple maths but what have we done?
- in the first case (x1) the output audio is the same as the input
- in the second case (2x) the output audio is now twice as loud (remember the correlation of difference between values)
- in the third example (x0.5) the output is now halves so is half as loud

It's a key reason to potentially use a bigger data structure (floats vs bytes, etc) and convert down when needed or just stick to floats, etc. You get all of these nice things for free - nothing complicated, nothing extra needed.

(if we kept to a strict 8bits, we need lots more strange math, shifts, and other unpleasant stuff that needs explaining to get the same result - in some cases you trade complexity for tight code and fast results)

But as it has been pointed out - I know nothing about audio and I'm also arrogant
But at least I will try to tell you why and tell you the truth...
« Last Edit: July 25, 2021, 14:55:05 by iWasAdam »

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 363
    • Midimaster Music Education Software
Re: DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #71 on: July 25, 2021, 15:39:54 »
I did not read your last 2 post... but: You are right, Adam! I now see that you are right! Can we now stop this discussion?

For all others,who like me believed that there is a defined MidPoint-Value of 128 in SF_MONO8:

You are not alone! Also Microsoft believes this:
On page 60 of the microsoft bulletin about PCM-format ( and hundreds of other documents) you can find this information:
http://www-mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/Docs/riffmci.pdf




« Last Edit: July 25, 2021, 16:08:31 by Midimaster »
See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

Offline iWasAdam

  • Hero Member
  • *****
  • Posts: 2478
Re: DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #72 on: July 26, 2021, 05:09:49 »
thankyou fo this amazing clarity of changing the subject.

we are now dealing with midpoints now - oh a see...   ;D

My you must be correct

Offline Baggey

  • Full Member
  • ***
  • Posts: 183
Re: DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #73 on: August 06, 2021, 20:40:39 »
Hi, Midimaster

Ive been experimenting with you're code and it is almost working for me! i am interested in your other idea of the microseconds function! Ive altered values and i am starting to get good results with short sounds at the expense of long sounds! And Vise Versa. Timming is more crucial than i thought for sending and recieving one Byte at a time with SpecBlitz.
Please keep up with the good WORK!

Kind Regards Baggey
Currently Running a PC that just Aint fast enough!?
ZX Spectrum 48k, NEXT, C64, ORIC Atmos 48K, Enterprise 128K, The SID chip.

Jesus was only famous because of his DAD.

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 363
    • Midimaster Music Education Software
Re: DONE! A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #74 on: August 07, 2021, 07:31:27 »
I published a new update of the RingBufferClass. Now you can download the Version 1.3 from post #59:
https://www.syntaxbomb.com/index.php/topic,8377.msg347049187.html#msg347049187


What is new?

ShowPercent
I added a ShowPercent() function where you no can check how full the buffer is. The value is from 0% to 100%. If the level reaches 80% the overruns protection can take pauses of 20msec to slowdown your too fast sending of data
Code: BlitzMax
  1. Global RingBuffer:RingBufferClass = New RingBufferClass
  2. ....
  3. Print "Level of ring buffer = " + RingBuffer.ShowPercent + "%"
  4.  


WITH_PROTECTION

Therefore I added a new flag WITH_PROTECTION:Int, which is default ON. But you can switch these pauses and run constantly (which now may risk buffer overruns)
Code: BlitzMax
  1. Global RingBuffer:RingBufferClass = New RingBufferClass
  2. RingBuffer.WITH_PROTECTION=0
  3.  


REPEAT_LAST_SAMPLE

The idea of one user to repeat the last data instead of sending SILENCE in cases, where the user sended to little data, turned out not to make any hearable difference. Both (sending last data or sending 0) had the same effect of  silence. so I did not add this flag.


Speed improvements

With consequent use of INTEGER POINTERs I could optimize the performance of the class with faktor 10. I removed my first idea of transfering the data with streams. Now all transfers are based on direct RAM access.


SendOne()

Data sent with SendOne() are now processed 10 times as often as before. So now every 100 user Data they are transmitted to the FreeAudio-Driver. This has no hearable effect and is only a test.

See my current project on PlayStore: 20Tracks-Audio-Player https://play.google.com/store/apps/details?id=midimaster.twentytrackd

 

SimplePortal 2.3.6 © 2008-2014, SimplePortal