Ooops
October 26, 2021, 03:38:11

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

Offline Scaremonger

  • Full Member
  • ***
  • Posts: 233
    • ITSpeedway - Ramblings of a geek!
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #45 on: March 27, 2021, 17:16:23 »
Looking good.

I notice you are creating the ring buffer from the format of the sample. If you created the buffer using shorts regardless of the format and then convert whatever samples are thrown at it using the Blitzmax audio convert function you wouldn't need to make sure the source format conforms.

You could then do something like this ( not real code):
Code: [Select]
Local audio:TRingbuffer = new TRingbuffer()
Local sample:TAudiosample.load("whatever.wav")
Audio.send( sample )

Send() can then receive an audio samples,  and convert it to the format you need for the ringbuffer

Code: [Select]
Method send( sample:TAudioSample)
Local stereo: TAudioSample = sample.convert( SF_STEREO16LE)

...

End method


Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #46 on: March 27, 2021, 18:06:08 »
Yes of course you can write a variant of the Send()-function with more features.

But the idea is, that Send() not needs complete TAudioSamples, but single sample values or a variable number of values.

You open it "empty" as a  STEREO or a MONO device. And now it stays open for throwing anything into it at any time.

If you already have "ready to play" TSounds or TSamples... why should you use the ringbuffer? You could already do this with the standard BlitzMax approach.

Some weeks ago here in the forum somebody ask for a simulation of the old ZX way to put bytes directly to the speaker port. The theme was "playing sounds in chunks". Do you remember? With TSounds smaller than 100msec we had a lot of crackling. With extrem short TSounds (< 20msec) it was completely un-usable.

https://www.syntaxbomb.com/index.php/topic,8285.0.html

With this ringbuffer you could simulate those old 8bit computer-speaker

At the moment the buffer expects chunks of 20msec without any crackling or noise. But I'm sure I can reduce this to 10msec or 5msec or 2msec also. This is not able with an TSound-based system.

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

Offline Scaremonger

  • Full Member
  • ***
  • Posts: 233
    • ITSpeedway - Ramblings of a geek!
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #47 on: March 27, 2021, 22:15:41 »
If you already have "ready to play" TSounds or TSamples... why should you use the ringbuffer? You could already do this with the standard BlitzMax approach.

Yes,  I see what you mean.

Some weeks ago here in the forum somebody ask for a simulation of the old ZX way to put bytes directly to the speaker port. The theme was "playing sounds in chunks".

This will make that a lot easier. 

Offline Baggey

  • Full Member
  • ***
  • Posts: 198
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #48 on: March 28, 2021, 11:37:44 »
@Derron - cant you just dump the steaming pile that is soloud in the bin where it belongs?
soloud is just a whole load of wrappers around another set of stuff NONE OF IT DOCUMENTED OR HAVING ANY NG EXAMPLES

without proper NG examples soloud is just a pile of crap. it is no use to ANY user without huge amount of knowlege about soloud and NG and how they interface.

It's one of the many many reasons NG is not well used - loads of wrappers for stuff no one know how to use.

I can write a wrapper for my blanket, but that just for me - if you want ANYONE to be able to use my blanket - then you MUST have documentation and examples - otherwise it's just useless code.


and if you dare to suggest that there are demos in the soloud folder I would SHOUT to you - they are in C NOT NG. and are as much use (to any user without detailed knowledge on how to convert C to workable NG - which is most people) as a knitted teapot!


To say 'lets take a low level audio experiment that directly hits the devices, and try to mash it into a fully fledged sound system with no documentation' is frankly absurd. Go stand in the corner...

Love that comment!  ;) :))

I feel like that with almost all coding these day's without examples and a simple way to get things working most coder's can get things of the ground!
I miss the old computer manuals that came with computers on how to program them with endless Examples. :-[

I can certainly code to a certain extent as ive written a Zx Spectrum Emulator using BlitzMax 1.5 out of the box. All be it 95% Working.

Watching this thread with interest!

As Ring buffer is what i think i need to access for my Blitz Max Spectrum emulator to play sound properl'y. Writing sample's in realtime as they occur from the OUT Function.
Being a hobby programmer all this SDL, .dll, wrappers, and link Modules files simply blows me away!  :o
You need to know how to install them and then how to use them.

Look forward to using something like this but it will need samples and an easy way to setup in BlitzMax.

Kudos to MidiMaster. Keep up the good work look forward to trying the PortAudio_Ring Buffer.

By the way, Ive only read the post upto this point so far.

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.    Resistance Is Futile! The code will be assimulated!

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: A new Audio-Out Approach in BlitzMax PortAudio
« Reply #49 on: March 28, 2021, 12:50:11 »
...
As Ring buffer is what i think i need to access for my Blitz Max Spectrum emulator to play sound properl'y. Writing sample's in realtime as they occur from the OUT Function.
...
Look forward to using something like this but it will need samples and an easy way to setup in BlitzMax.
...
Kind Regards Baggey

The use of the ringbuffer is simpler than your old approach inside the OUT_CHUNKS()-function!

old function:
Code: BlitzMax
  1. Function OUT_CHUNKS(Port%, Value%)
  2.         Const SAMPLE_RATE%=44100
  3.         Global NextSample:TAudioSample, PlayTime%
  4.         If Counter=0
  5.                 Print "Sample created"
  6.                 NextSample = CreateAudioSample(4410, SAMPLE_RATE, SF_MONO8 )
  7.         EndIf
  8.  
  9.         Select Value & 8
  10.                 Case 0
  11.                         NextSample.Samples[Counter]=50
  12.                 Case 8
  13.                         NextSample.Samples[Counter]=200
  14.         End Select
  15.         Counter=Counter + 1
  16.         If counter=4408
  17.                 NextSample.Samples[4408]=0    
  18.                 NextSample.Samples[4409]=0    
  19.                 Local Audio:TSound = LoadSound( NextSample )
  20.                 Repeat
  21.                         Delay 1
  22.                 Until PlayTime<MilliSecs()
  23.                 Print "play TSound"
  24.                 PlayTime=MilliSecs()+100
  25.                 PlaySound Audio
  26.                 Counter=0
  27.         EndIf
  28. End Function
You replace the TAudioSample with a simple Array, then collect the values here. And when they reach f.e. 441 bytes you send them to the BufferClass(). Thats all. So the access is faster you can reach 10msec-chunks without having crackles.

possible new (theoretic) approach function:
Code: BlitzMax
  1. Function OUT_CHUNKS(Port%, Value%)
  2.          Global NextSample:BYTE[441], PlayTime%
  3.  
  4.         Select Value & 8
  5.                 Case 0
  6.                         NextSample[Counter]=50
  7.                 Case 8
  8.                         NextSample[Counter]=200
  9.         End Select
  10.         Counter=Counter + 1
  11.         If Counter=441
  12.                Delay PlayTime-Millisecs()
  13.                RingBuffer.Send NextSample
  14.                PlayTime=PlayTime+10            
  15.                Counter=0
  16.         Endif
  17. End Function

This is theoretic! At the moment I have not implemented SF_MONO8 as Format and the use of the DELAY is not very elegant. This would interrupt the emulator for a to long time.
« Last Edit: March 28, 2021, 12:57:24 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: 364
    • Midimaster Music Education Software
Now Threads and support for SF_MONO8
« Reply #50 on: March 29, 2021, 18:42:59 »
Another step brings us to more usability. You can now already test the RingBuffer-Class in your apps. I now use Threads for more stability. This means that now BlitzMax-NG is the better choice for this

How to add?

I changed the calling of the Ringbuffer:


Code: BlitzMax
  1. RingBufferClass.SetDriver("FreeAudio Multimedia")
  2. Global RingBuffer:RingBufferClass = New RingBufferClass
  3.  
  4. Global Source:TAudioSample=LoadAudioSample("Test8.wav")
  5.  
  6. RingBuffer.CreateNew(Source.Format , Source.Hertz , 4)
  7. SendSize = RingBuffer.IntervalSamples()
  8. SEND_TIME= RingBuffer.IntervalTime()
  9.  

For starting the RingBuffer you need to set its driver to any FreeAudio-Driver.

Then set the FORMAT to one of: SF_MONO8 or SF_MONO16LE or SF_STEREO16LE

Then set the HERTZ to a multiple of 1000 for best results, There is no need to use the Source.Hertz

The last parameter defines a symbolic "latency" of a InBuffer where you later transfer your datas to the Ringbuffer.
A value of 4 is ok in the most cases. If you hear crackles use higher values.

Later you will send the datas in intervalls to the Ringbuffer. Therefore you need to know how many data exactly you have to send in which time.


Now start two Threads:

The first is for the Ringbuffers internal timing to send datas to the Audio-Device
The second is for you to send your datas right in time.

Code: BlitzMax
  1. Local WatchThread:TThread=CreateThread(WatchLoop, "")
  2. Local SendThread:TThread=CreateThread(SendLoop, "")
  3.  
  4. Function WatchLoop:Object(data:Object)
  5.         Repeat
  6.                 Delay 3
  7.                 RingBuffer.Watch
  8.         Forever
  9. End Function
  10.  
  11. Function SendLoop:Object(data:Object)
  12.         Repeat
  13.                 Delay 4
  14.                 If WriteTime<MilliSecs()
  15.                         ' this cares about really 20msec timing:
  16.                         WriteTime =WriteTime + SEND_TIME
  17.                         SendAudio
  18.                 EndIf
  19.         Forever
  20. End Function
  21.  


Your SendAudio()-Function cares about sending the datas to the Ringbuffers:
Code: BlitzMax
  1. Function SendAudio()
  2.                 Local AudioArray:Short[SendSize]
  3.                 SourceStream.Seek(ReadPointer)
  4.                 For Local i%=0 To SendSize-1
  5.                         AudioArray[i] = ...whatever
  6.                 Next
  7.                 RingBuffer.Send AudioArray
  8.                 ReadPointer= ReadPointer+SendSize*2
  9. End Function
  10.  


This is an executable code, if you download the 8bit AudioFile Test8.wav

Code: BlitzMax
  1. SuperStrict
  2.  
  3. Global SendSize% , ReadPointer%
  4.  
  5. Graphics 800, 600
  6. RingBufferClass.SetDriver("FreeAudio")
  7.  
  8. Global  SEND_TIME:Int, raus%=0
  9.  
  10. Global Source:TAudioSample=LoadAudioSample("Test8.wav")
  11. Global SourceStream:TStream = CreateRamStream(Source.Samples,Source.Length*4,True,True)
  12. Print  Source.Hertz
  13.  
  14. Global RingBuffer:RingBufferClass=New RingBufferClass
  15. RingBuffer.CreateNew(Source.Format, 22000, 4)
  16. SendSize = RingBuffer.IntervalSamples()
  17. SEND_TIME= RingBuffer.IntervalTime()
  18. Print SendSize + " " + send_time
  19.  
  20. Global WriteTime%=MilliSecs()
  21.  
  22. Local WatchThread:TThread=CreateThread(WatchLoop, "")
  23.  
  24. Local SendThread:TThread=CreateThread(SendLoop, "")
  25.  
  26.  
  27. Repeat
  28.         Cls
  29.         SetColor 255,255,255
  30.         DrawText "click here to PAUSE:",200,370
  31.  
  32.         DrawRect 200,400,100,100
  33.         SetColor 1,1,1
  34.         DrawRect 201,401,98,98
  35.  
  36.         Flip 0
  37. Until AppTerminate()
  38. End
  39.  
  40.  
  41. Function WatchLoop:Object(data:Object)
  42.         Repeat
  43.                 Delay 3
  44.                 RingBuffer.Watch
  45.         Until raus=1
  46. End Function
  47.  
  48.  
  49. Function SendLoop:Object(data:Object)
  50.         Repeat
  51.                 Delay 4
  52.                 If WriteTime<MilliSecs()
  53.                         ' this cares about really 20msec timing:
  54.                         WriteTime =WriteTime + SEND_TIME
  55.                         If MouseDown(1)=0
  56.                                 SendAudio
  57.                         EndIf
  58.                 EndIf
  59.         Until raus=1
  60. End Function
  61.  
  62.  
  63. Function SendAudio()
  64.                 Local AudioArray:Short[SendSize]
  65.                 SourceStream.Seek(ReadPointer)
  66.                 For Local i%=0 To SendSize-1
  67.                         AudioArray[i] = SourceStream.ReadShort
  68.                 Next
  69.                 RingBuffer.Send AudioArray
  70.  
  71.                 ReadPointer=(ReadPointer+SendSize*2) Mod ((Source.length-SendSize)*2)
  72. End Function
  73.  
  74. '------------------------------------------------------------------------
  75.  
  76. Type RingBufferClass
  77.         Global MyDriver$
  78.         Global BufferMutex:TMutex=CreateMutex()
  79.  
  80.         Field CHANNELS:Int, CHUNK_SIZE:Int, BUFFER_SAMPLES:Int, BUFFER_SIZE:Int
  81.         Field FORMAT:Int, CHUNK_TIME:Int, HERTZ:Int, BITS:Int, ZERO:Int
  82.         Field WritePointer:Int, ReadPointer:Int, RingPointer:Int
  83.         Field WriteTime:Int, WatchTime:Int
  84.  
  85.         Field RingBuffer:TAudioSample, InBuffer:TAudioSample, Sound:TSound
  86.         Field RingStream:TStream, InBufferStream:TStream
  87.        
  88.  
  89.         Function SetDriver(Driver$)
  90.                 If MyDriver<>"" Return
  91.                 If Driver.contains("FreeAudio")=False
  92.                         Notify "wrong AudioDriver"
  93.                         End
  94.                 EndIf
  95.                 MyDriver = Driver
  96.                 SetAudioDriver(MyDriver)
  97.         End Function
  98.        
  99.        
  100.         Method CreateNew(Format%, Hertz%, Latency%)
  101.                         ' HERTZ should be a multiple of 1000 for CHUNK_TIME=10, 20, 40 or 50
  102.                         ' HERTZ can also be 44100 when CHUNK_TIME=20 or 40
  103.                         '
  104.                         ' FORMAT can be SF_MONO8 or SF_MONO16LE or SF_STEREO16LE
  105.                         '
  106.                         ' LATENCY can be from 1 to 32
  107.                         ' 2=extrem small, 4=normal size,  8..=secure sizes
  108.                         '      
  109.                         If MyDriver=""
  110.                                         Notify "No AudioDriver selected"
  111.                                         End
  112.                         EndIf
  113.                         Self.HERTZ=Hertz
  114.                         Self.FORMAT=Format
  115.                         DefineBuffer Latency
  116.                         ClearBuffer
  117.                         WatchTime=MilliSecs()
  118.                         PlaySound Sound
  119.         End Method
  120.        
  121.        
  122.         Private Method DefineBuffer(Latency%)
  123.                 CHUNK_TIME=20
  124.                 Select FORMAT
  125.                         Case SF_MONO16LE
  126.                                 BITS=16
  127.                                 CHANNELS=1
  128.                         Case SF_STEREO16LE
  129.                                 BITS=16
  130.                                 CHANNELS=2
  131.                         Case SF_MONO8
  132.                                 BITS=8
  133.                                 CHANNELS=1                                     
  134.                         Default
  135.                                 Notify "Audio format not supported"
  136.                                 End
  137.                 End Select      
  138.                 CHUNK_SIZE     = HERTZ * CHUNK_TIME * CHANNELS * BITS/8/1000
  139.                 BUFFER_SAMPLES = 4*Latency * HERTZ * CHUNK_TIME             /1000
  140.                 BUFFER_SIZE    = BUFFER_SAMPLES * BITS/8 *CHANNELS
  141.                
  142.                 RingBuffer     = CreateAudioSample(BUFFER_SAMPLES, HERTZ, FORMAT)
  143.                 InBuffer       = CreateAudioSample(BUFFER_SAMPLES, HERTZ, FORMAT)
  144.                 Local fa_sound:Byte Ptr  = fa_CreateSound( BUFFER_SAMPLES-1, BITS, CHANNELS, HERTZ, RingBuffer.Samples, $80000000 )
  145.                 Sound          = TFreeAudioSound.CreateWithSound( fa_sound, Null)
  146.                 RingStream     = CreateRamStream(RingBuffer.Samples , Buffer_size,True,True)
  147.                 InBufferStream = CreateRamStream(InBuffer.Samples,Buffer_size,True,True)
  148.                 RingPointer    =  BUFFER_SIZE/4
  149.         End Method
  150.        
  151.        
  152.         Public Method Watch()
  153.                 If WatchTime<MilliSecs()
  154.                         WatchTime = WatchTime + CHUNK_TIME
  155.                         SendOneChunk
  156.                 EndIf
  157.         End Method
  158.  
  159.  
  160.         Private Method SendOneChunk()
  161.                 LockMutex BufferMutex
  162.                         Local ReadPointerMod% = ReadPointer Mod BUFFER_SIZE
  163.                         InBufferStream.Seek ReadPointerMod
  164.                         RingStream.Seek RingPointer
  165.  
  166.                         If ReadPointer + CHUNK_SIZE > WritePointer
  167.                                 Local Maxi%=WritePointer-ReadPointer
  168.                                 For Local i:Int = 0 To Maxi-1
  169.                                                 RingStream.WriteByte InBufferStream.ReadByte()
  170.                                 Next
  171.                                 For Local i:Int = Maxi To CHUNK_SIZE-1
  172.                                                 RingStream.WriteByte ZERO
  173.                                 Next
  174.                                 ReadPointer=ReadPointer + Maxi
  175.                         Else
  176.                                 For Local i:Int = 0 To CHUNK_SIZE-1
  177.                                         RingStream.WriteByte InBufferStream.ReadByte()
  178.                                 Next
  179.                                 ReadPointer=ReadPointer + CHUNK_SIZE
  180.                         EndIf
  181.                         RingPointer=(RingPointer + CHUNK_SIZE) Mod (BUFFER_SIZE)
  182.                 UnlockMutex BufferMutex
  183.         End Method
  184.  
  185.  
  186.         Private Method ClearBuffer()
  187.                         If BITS=8 ZERO=127
  188.                         For Local i:Int =0 To BUFFER_SIZE
  189.                                 RingBuffer.Samples[i]=ZERO
  190.                         Next   
  191.         End Method
  192.        
  193.        
  194.         Public Method Send(AudioArray:Short[])
  195.                 LockMutex BufferMutex
  196.                 Local WritePointerMod% = WritePointer Mod BUFFER_SIZE
  197.  
  198.                 InBufferStream.Seek WritePointerMod
  199.                
  200.                 If AudioArray.Length*2 + (WritePointerMod) <= BUFFER_SIZE
  201.                         For Local i%=0 To AudioArray.Length-1
  202.                                 InBufferStream.WriteShort AudioArray[i]
  203.                         Next
  204.                        
  205.                 Else
  206.                         Local Maxi% = BUFFER_SIZE-WritePointerMod
  207.                        
  208.                         For Local i%=0 To Maxi/2-1
  209.                                 InBufferStream.WriteShort AudioArray[i]
  210.                         Next
  211.                         InBufferStream.Seek 0
  212.                         For Local i%=Maxi/2 To AudioArray.Length-1
  213.                                 InBufferStream.WriteShort AudioArray[i]
  214.                         Next
  215.                 EndIf
  216.                 WritePointer=WritePointer + AudioArray.Length*2
  217.                 UnlockMutex BufferMutex
  218.         End Method
  219.  
  220.        
  221.         Public Method IntervalSamples:Int()
  222.                 Return CHUNK_SIZE/2
  223.         End Method
  224.  
  225.        
  226.         Public Method IntervalTime:Int()
  227.                 Return CHUNK_TIME
  228.         End Method
  229. End Type
  230.  
  231.  
  232.  
  233.  




« Last Edit: March 29, 2021, 18:53:25 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: 2483
Re: A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #51 on: March 29, 2021, 18:55:19 »
well done.
You might want to check your buffer filling with something that doesn't have a beat though.
It looks like there is a tiny dropout (a few millisecs) that seems to be regular

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #52 on: March 29, 2021, 19:16:47 »
I think the filling of the buffers runs 100% perfect. I checked it with sending thousand of samples and compared the bytes before sending them with the bytes reached at the PlaySound.

The dropouts maybe depend on the latency parameter we set.  Latency differs related to the device we use.
Here on my Windows 10 I can use 2 without problems and 4 is very stabile.

Maybe a value of 8 or 16 would already bring a improvement:
Code: BlitzMax
  1. RingBuffer.CreateNew(Source.Format, 22000, 16)
 


There was one thing I cannot explain with FreeAudio and SF_MONO8. I heard a rhythmic crackle of 1 byte size even though the byte transfer was all correct.

So I had to react with a "unlogic" workaround and the crackle has gone:

This is the code I would have expected to be logic:
Code: BlitzMax
  1. RingBuffer       = CreateAudioSample(BUFFER_SAMPLES, HERTZ, FORMAT)
  2. Local fa_sound:Byte Ptr  = fa_CreateSound( BUFFER_SAMPLES, BITS, CHANNELS, HERTZ, RingBuffer.Samples, $80000000 )

This workaround was necessary:
Code: BlitzMax
  1. RingBuffer       = CreateAudioSample(BUFFER_SAMPLES, HERTZ, FORMAT)
  2. Local fa_sound:Byte Ptr  = fa_CreateSound( BUFFER_SAMPLES-1, BITS, CHANNELS, HERTZ, RingBuffer.Samples, $80000000 )


Perhaps this is a Windows version behavior? And on your computer it is not necessary?
« Last Edit: March 29, 2021, 19:21:51 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: 2483
Re: A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #53 on: March 29, 2021, 20:06:35 »
Yep, the one thing you will get with windows is a whole load of ‘this worked, this didn’t’ lol
Tbh I treat windows as mess of pain- you will never get stuff to work everywhere...

Offline Scaremonger

  • Full Member
  • ***
  • Posts: 233
    • ITSpeedway - Ramblings of a geek!
Re: A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #54 on: March 30, 2021, 07:11:15 »
I tried the code from post 51 on my dev laptop running Linux Mint 18 and it sounded great, but there seems to be an issue when it reaches the end of the music.

I ran it a few times, clicking, holding and most of the time I received a segmentation fault and sometimes a wicked screeching noise first.

The sample is 1010288 bytes, but there is nothing to stop the ReadPointer in SendAudio() from extending past the end of the music. The loop carries on attempting to read from the SourceStream past it's end. You can see this by simply adding a print statement and watching the output....

Code: [Select]
Function SendAudio()
                Local AudioArray:Short[SendSize]
                SourceStream.Seek(ReadPointer)
                For Local i%=0 To SendSize-1
Print source.length+", "+sendsize + ", " +readpointer + ", "+ i
                        AudioArray[i] = SourceStream.ReadShort
                Next
                RingBuffer.Send AudioArray
 
                ReadPointer=(ReadPointer+SendSize*2) Mod ((Source.length-SendSize)*2)
End Function

1010288, 220, 1011560, 63
1010288, 220, 1011560, 64
1010288, 220, 1011560, 65
1010288, 220, 1011560, 66
1010288, 220, 1011560, 67
1010288, 220, 1011560, 68
Segmentation fault (core dumped)

Process complete


I'm running on BlitzMax NG:

BCC Ver: bcc[ng] release 0.129
BMK Ver: bmk 3.45 mt-linux-x64 / gcc 070500 (cpu x4)
GCC Version: 7

Offline Derron

  • Hero Member
  • *****
  • Posts: 3674
Re: A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #55 on: March 30, 2021, 07:41:45 »
I ran it a few times, clicking, holding and most of the time I received a segmentation fault and sometimes a wicked screeching noise first.

Code: [Select]
ReadPointer=(ReadPointer+SendSize*2) Mod ((Source.length-SendSize)*2)

This is the culprit I think.
sendsize = 10
length = 100
readpointer = 80

-> (80 + 10*2) mod ((100-10)*2) = 100 mod 180 = 100

While "modulo" looks like a smart way to do things it often leads to such little mistakes.

You want "readpointer" to grow by "sendsize*2" and to wrap at "source.length", not at "source.length minus sendsize-something". Only subtract at the end if you were talking about a non-looping buffer. Your code ensures there is "space left" at the end of the buffer/block to use (and this only if you replaced "((Source.length-SendSize)*2)" with "(Source.length-SendSize*2)".


Code: [Select]
ReadPointer=(ReadPointer+SendSize*2) Mod Source.length

This should loop "around" the source block. So "pointer + readmount mod length".


bye
Ron



« Last Edit: March 30, 2021, 07:44:43 by Derron »

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #56 on: March 30, 2021, 09:39:42 »
This is a little "copy&paste"-bug reamining from the former SF_16MONO-Version. As I never listen to the end of the source, I didi not check it. But this is only the test-code's bug. Not a bug in the Class.

The OP has to take care about checking the limits of the source. So this was only a "turn around 100% before the song ends" MOD calculation. The last possible chunk that can be sent varies depending on the parameters you use in the Class. To ensure that it works 100% I substracted SendSize from the end of the source.
this works for 16bit:
Code: [Select]
ReadPointer=(ReadPointer+SendSize*2) Mod ((Source.length-SendSize)*2)
and this for 8bit:
Code: [Select]
ReadPointer=(ReadPointer+SendSize*2) Mod (Source.length-SendSize*2)


More interesting are the crackles Scaremonger descripes. Are they only at the end of the source or could you hear them also before?


Now I decided to finish the investigation and offer a "final" version which uses  always SF_STEREO16 intern and converts received samples to this format before adding them to the ringbuffer. This frees me from the need to offern several ways for a lot of hardware/software cases.



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

Offline Derron

  • Hero Member
  • *****
  • Posts: 3674
Re: A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #57 on: March 30, 2021, 10:02:18 »
this works for 16bit:
Code: [Select]
ReadPointer=(ReadPointer+SendSize*2) Mod ((Source.length-SendSize)*2)

ReadPointer describes where to read from "source" ?

What if this are values in use there (before calling this line)
ReadPointer = 100
SendSize = 10
Source.length = 100

ReadPointer = (100+10*2) mod ((100 - 10) * 2)
ReadPointer = 120 mod 180
ReadPointer = 120

BUT ... the stream has only a size/length of 100 ... so it will try to read at a position not available
You would need to do another "mod source.length" to think of the "source" as a ring of data.


bye
Ron

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Re: A new Audio-Out Approach in BlitzMax FreeAudio RingBuffer
« Reply #58 on: March 30, 2021, 12:57:57 »
In a SF_MONO16LE the size of the TAudioSample is double of the TAudioSample.Length

So if TAudioSample.Length=100 the ReadPointer can run from 0 to 199

in my test-surrounding the ReadPointer was only allowed to run to (TAudioSample.Length-SendSize)*2, which means from 0 to 179.

In the final version this is a thing the OP has to care about himself. Only he knows the sources of the samples values. Maybe they come from a live sinus calculation. Therefore he does not need this line anyway. My next post will show some code examples.


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

Offline Midimaster

  • Sr. Member
  • ****
  • Posts: 364
    • Midimaster Music Education Software
Final Version of the FreeAudio-Ringbuffer
« Reply #59 on: March 30, 2021, 14:06:00 »
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.3.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.3 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"
This behavior can be switched of by RingBuffer.WITH_PROTECTION=0


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


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!
« Last Edit: August 09, 2021, 09:26:44 by Midimaster »
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