Realtime sound sample manipulation

BlitzMax Forums/BlitzMax Programming/Realtime sound sample manipulation

I'd like to write to the samplebuffer directly, and have the changes played back the next time the sample loops.

I've done this in PowerBasic in the past with a Windows only solution.

In Blitzmax, it seems manipulating a buffer used in CreateStaticAudioSample(), doesn't yield any results unless i reload the sound.

Is it possible in native Bmax or do I need a lib?

You could do it with OpenAL, creating your own audio buffer data as necessary.

I have done it with the FMOD module, but it's a bit of a pain and involves some C programming (unless the garbage collector has improved to the point where the callback could be written in Blitz without bizarre errors, since I last tried.)

I think Taron recently did something similar to what you're attempting, so invade one of his threads and ask.

Well, well... I'm really just doing hardcore looping by not only reloading a sample piece, but the generating and reloading the whole thing, which is everything but what you'd want. My goal hasn't been to do a realtime synth, because during a game it would only additionally burden the cpu (unless multithreaded and even then I would likely find a better use for the second thread...).

Anyway, I do love the idea of a realtime synth and also keep my eyes open for good suggestions using bmax. I do, however, want to learn more about including c libs and code snippits, since I am natively rather a C guy, yet not in regards to entire applications but rather for writing plugins. I've never done any audio programming, so this is my first time ever. It's remarkably dissimilar to graphics programming, due to the 1 dimentional aspect of it and the need to do many things on the fly in one go. Makes you think differently, but it's great! In the last two weeks I've learned unbelievable amounts about sound programming, but also interface coding and the likes. All stuff I've never done to that extend or at all.

As for Fmod, I think I just don't like to make myself depend on things, particularely if they are somewhat restrictive (license stuff included). Souns paradox for new BlitzMax programmer to say, but well... I hope you know what I mean.

Two things to find out:
- implementing C code
- Basic DirectAudio or OpenAL coding in C

That's all, and I'm sure we'll be ready to rock! 8)

Thanks guys.

For now it looks like OpenAL or some native Windows stuff if I can dig it out of my archives then.

this code should/may still work with freeaudio multimedia drivers:

tfloat synth test

Thanks skidracer, but I can't get the Freeaudio driver to initialise.

[EDIT]: "Freeaudio Multimedia" does initialise, but nothing happens when I write to the sample buffer after Loadsound has been called

Am I missing something?

Below is my testcode, clipped together from the examples in the other thread.

With "FreeAudio DirectSound" I get the noise when it's unrem'ed and run before LoadSound(), but I can't change the samples in the mainloop.


SuperStrict

Graphics 400,300


'Const AudioDriverName:String = "FreeAudio Default"
Const AudioDriverName:String = "FreeAudio DirectSound"

If Not SetAudioDriver(AudioDriverName)	'cludge around build order problems

	RuntimeError "can't set ~q" + AudioDriverName + "~q Audio driver"

EndIf


?BigEndian
Const SF_STEREO16:Int=SF_STEREO16BE
Const SF_MONO16:Int=SF_MONO16BE
?LittleEndian
Const SF_STEREO16:Int=SF_STEREO16LE
Const SF_MONO16:Int=SF_MONO16LE
?
	
Local sample:TAudioSample
	Local channel:TChannel
	Local sound:TSound

	Local bits:Int = 16
	Local channels:Int = 2

	Local format:Int 

	Local fragsize:Int = 8192
	
	Local writepos:Int

	
	If bits=16
		If channels=2			
			format=SF_STEREO16
		Else
			format=SF_MONO16
		EndIf			
	Else
		If channels=2
			format=SF_STEREO8
		Else
			format=SF_MONO8
		EndIf
	EndIf	


	sample=CreateAudioSample(fragsize*4,44100,format)	'SF_MONO8)
	MemClear sample.samples,sample.capacity

	Rem make some noise		
	Local s:Short Ptr = Short Ptr sample.samples
	For Local q:Int = 0 To fragsize*4*channels
		s[q] = Rand(-2000,2000)
	Next		
	End Rem
	
	channel=AllocChannel() 
	sound=LoadSound(sample,True)
	PlaySound sound,channel



While Not KeyHit(KEY_ESCAPE)


		Local s:Short Ptr = Short Ptr sample.samples '+p*channels
		'''Music.FillBuffer(Int(s), fragsize*4)
		
		For Local q:Int = 0 To fragsize*4*channels
			s[q] = Rand(-2000,2000)
		Next	


	DebugLog ChannelPlaying(channel)

	Delay 100


Wend


Hi Peter, sorry to waste your time, I'll try and have a look at freeaudio streaming this weekend hopefully.

Don't be sorry, it's great if you can look into it.

It might very well be me not quite grasping the finer details.

I updated your code like shown below. It runs and readpos returns what looks like valid results to me by casting the channel to a TFreeAudioChannel , but I still get no sound.

SuperStrict

Graphics 400,300


'Const AudioDriverName:String = "FreeAudio Default"
Const AudioDriverName:String = "FreeAudio Multimedia"

If Not SetAudioDriver(AudioDriverName)	'cludge around build order problems

	RuntimeError "can't set ~q" + AudioDriverName + "~q Audio driver"

EndIf


?BigEndian
Const SF_STEREO16=SF_STEREO16BE
Const SF_MONO16=SF_MONO16BE
?LittleEndian
Const SF_STEREO16:Int=SF_STEREO16LE
Const SF_MONO16:Int=SF_MONO16LE
?

Type TSynth

	Global timer:TTimer
	Global synthlist:TList=New TList
	
	Field sample:TAudioSample
	Field channel:TChannel
	Field sound:TSound

	Field bits:Int
	Field channels:Int
	Field format:Int
	Field fragsize:Int	
	Field writepos:Int
	
	Function eventhook:Object(id%,data:Object,context:Object)
		Local synth:TSynth
		If context=timer 
			For synth=EachIn synthlist
				synth.Poll
			Next
			Return Null
		EndIf
	End Function
	
	Method Poll()
		Local readpos:Int
		Local count:Int
		readpos=TFreeAudioChannel(channel).Position()/channels
		If readpos=0 Return 'this channel is stopped
		count=((readpos+fragsize*2-writepos)/fragsize)
		
		
		DebugLog readpos
		
		While count
			Select bits
				Case 8
					Mix8
				Case 16
					Mix16
			End Select
			writepos=writepos+fragsize
			count=count-1
		Wend
	End Method

' mix the next .fragsize samples into .sample at (.writepos mod .fragsize)

	Method Mix8()
	End Method

	Method Mix16()
	End Method
		
	Method CreateSynth:TSynth(frag:Int=8192,_bits:Int=16,_channels:Int=2)
		fragsize=frag
		bits=_bits
		channels=_channels
		If bits=16
			If channels=2			
				format=SF_STEREO16
			Else
				format=SF_MONO16
			EndIf			
		Else
			If channels=2
				format=SF_STEREO8
			Else
				format=SF_MONO8
			EndIf
		EndIf		
		sample=CreateAudioSample(fragsize*4,44100,format)	'SF_MONO8)
		MemClear sample.samples,sample.capacity
		channel=AllocChannel() 
		sound=LoadSound(sample,True)
		PlaySound sound,channel
		If Not timer
			timer=CreateTimer(100)
			AddHook EmitEventHook,eventhook,timer
		EndIf
		synthlist.AddLast Self
		Return Self
	End Method
	
	Function Close()
		If timer
			RemoveHook EmitEventHook,eventhook
			StopTimer timer
			timer=Null
		EndIf
	End Function

End Type

Type TSweepSynth Extends TSynth

	Field rad#,radf#

	Method Mix8()
		Local b:Byte Ptr
		Local p:Int,i:Int	
		p=writepos Mod (fragsize*4)
		b=sample.samples+p*channels
		For i=0 Until fragsize*channels		
			rad=rad+radf/channels
			radf=radf+.00001/channels
			b[i]=128+127*Sin(rad)
		Next	
	End Method

	Method Mix16()
		Local s:Short Ptr
		Local p:Int,i:Int
		p=writepos Mod (fragsize*4)
		s=Short Ptr sample.samples+p*channels
		For i=0 Until fragsize*channels
			rad=rad+radf/channels
			radf=radf+.00001/channels
			s[i]=32767*Sin(rad)
		Next	
	End Method

	Method CreateSweepSynth:TSweepSynth(frag:Int=8192,_bits:Int=16,_channels:Int=2)
		CreateSynth frag,_bits,_channels
		Return Self
	End Method

End Type


Function CreateSweepSynth:TSynth(fragsize:Int,bits:Int,channels:Int)
	Return New TSweepSynth.CreateSweepSynth(fragsize,bits,channels)
End Function

CreateSweepSynth 8192/8,16,2

While True
	WaitEvent
	Print "yo!"
Wend





If your interested I'll be releasing the new MaxMod2 module soonish.
A simple example using MaxMod2.CallbackStream works like this ATM...
Import MaxMod2.PortAudio
Import MaxMod2.CallbackStream

Local Chn:TChannel = CreateAudioCallbackStream(New TAudio)
Repeat
	Delay 1
Until ChannelPlaying(Chn)=0

Type TAudio Extends TCallbackStream

	Field p!, p1!

	Method New()
		SampleRate = 11025							' set required samplerate
		Channels   = 1								' set the number of channels
		Bits       = 16							' set bits per channel
	EndMethod

	Method FillBuffer:Int(output:Byte Ptr,size:Int)

		Local AudioPtr:Short Ptr = Short Ptr(output)		' get our pointer to the output
		Local n:Int,av!							' make some local variables
		For n=0 Until size/2						' loop to process the output buffer
			av=Sin( Sin(p1^2)*ATan(p^3)*20000 )*5000	' calculate our audio value
			If p1<1 Then av:*p1						' add a simple fade in
			AudioPtr[n] = av						' apply audio value to output
			p:+0.5 ; p1:+0.00009					' increment for next pass
			If p>1000000 Exit;						' stop when p>1000000
		Next
		Return n*2								' return number a bytes written to output.
												' a return value less than 'size' 
												' causes the stream to stop
	EndMethod

EndType

latency isnt to bad at about a 10th of a second, and of course all the standard TChannel commands work on the stream too.

*EDIT* excuse the borked tabbing

@REDi : Looks just like what I'm looking for.

How do I set the bufer size?

I'll be looking forward to the release of maxmod2 !
Is there a beta? :-)

How do I set the bufer size?

The circular buffer size is handled internally and is normally <100 millisecs (depending on audio driver used), but the amount of audio you want to play is up to you, as soon as you return less than the requested amount of audio it assumes you're all done and ends when its finished playing what you've given it, so you can play a couple of millisecs or hours worth :)

Is there a beta? :-)

the first couple of releases are always beta for me, until people stop finding problems ;) I'll hopefully be able to release in a couple of days.

So if my aim is to play a looping 32ms tone, I'll handle the looping by
writing it at least 1102/353 (3.1'ish) times to the buffer, and remember the loop offset between runs?
(assuming 32ms ~ 353 samples at 11025.)

[EDIT]: Okay, to make it easy on myself, I 'll write it 4 times?... :-)