Real-time audio input

BlitzMax Forums/BlitzMax Programming/Real-time audio input

Is there a way in BliztMax to process audio input in real-time? For example, to display an oscilloscope or other "visualizations" of audio input?

s.armstrong did a portaudio mod, cant remember syncmod command of the top of my head and I'm not at home, but that will do what you want.

I think its axe.portaudio

Eek. I've looked through it and haven't got a clue how to pull in sound data and manipulate it.

-Ryan

he he

Its worth having a good go at its a rather nice lib for realtime stuff...

can you give an example of what you are interested in doing and on what OS?

I want to build a real-time audio analyzer/visualizer that allows me to choose specific frequency ranges to analyze and display. I ultimately want this to be used on stage with my band as a projected display that responds to each of our instruments. It would be like G-Force (http://www.soundspectrum.com/g-force/), but whatever control I need over it.

It would be on XP, for now.

I'm not sure how to analyse a specific frequency range, not somthing I've looked into, if I get chance I'll but together a simple microphone oscilloscope.

hacked this together quick, bit rough and ready but it gives you the idea

dont forget to syncmods -u xxxx -p xxxx axe.portaudio !

Import axe.portaudio


Global astream:Byte Ptr

''''''''''''''''''''''''' stuff missing from port audio
Extern
	Function Pa_GetDefaultOutputDeviceID:Int()
EndExtern

Const paInt8	:Int=32
Const paUInt8	:Int=64
Const paInt16	:Int=2
'''''''''''''''''''''''''' end of missing stuff

Graphics 640,480

Global buf:Float[256]

error( Pa_Initialize() ,"init")

' set the mix buffer running....
playstreamPA(astream,myCallback)	


While Not KeyDown(key_escape)


		Cls
		For Local n:Int=0 To 255

			DrawLine 320,n+100,320+buf[n],n+100
		Next

		Flip


Wend


Pa_CloseStream(astream)

End



Function myCallback:Int( inputBuffer:Float Ptr, outputBuffer:Short Ptr,..
                           framesPerBuffer:Int,outTime:Double, userData:Byte Ptr )
	For Local n:Int=0 To 255
		buf[n]=inputBuffer[0]*128
		inputbuffer:+1
	Next
EndFunction





Function error(e:Int,s:String)
	If e=0 Then Return
	Pa_Terminate()
	Print "error "+e+" in "+s
	Print "message "+String.FromCString(Pa_GetErrorText(e))
	End
EndFunction	


Function playstreamPA(stream:Byte Ptr Var,callback:Byte Ptr)

	error(.. 
	Pa_OpenStream( Varptr stream,Pa_GetDefaultOutputDeviceID(),2,paFloat32,Null,..
						paNoDevice,0,paInt8,Null,44100,256,0,paClipOff,..
			              			Callback,Null )..
	,"open stream")	
	
	error( Pa_StartStream( stream ),"start stream" )
	Print "playing"



EndFunction


Thanks! I was getting close but kept crashing. This will give me a good start!

be careful what you do in the callback, it should not call other functions and should return as quickly as possible

let us know how you get on - drop us a mail by all means

I'm getting this error now:

"Unhandled Exception:GC clrMemBit: membit not set!"

It seems that this occurs quicker the more I have going on the screen. With just plain old bars, it may not crash unless I really crank up the frames per buffer (say, above 256). When I throw in my little particle "engine" with creation, decay and drawing functions, it will crash after maybe 10 seconds. Obviously, I need to limit the number of particles in existance at one time, but is there a fix for this?

Thanks for the help so far!

you're not doing *any* drawing or calculation in the callback are you?

hard to say without seeing any code...

No, I've left the callback function just as you've written it, except for changing the multiplication factor for Buf[n] and using a variable for the for..to limit instead of 255.

All of my calculations, drawing, etc are contained in the main loop. Here's the code, so far:

Import axe.portaudio                    
Include "particles.bmx"              
'Include "explosions.bmx"
'Include "screenflash.bmx"

Global astream:Byte Ptr

''''''''''''''''''''''''' stuff missing from port audio
Extern
	Function Pa_GetDefaultOutputDeviceID:Int()
EndExtern

Const paInt8	:Int=32
Const paUInt8	:Int=64
Const paInt16	:Int=2
'''''''''''''''''''''''''' end of missing stuff

Graphics 1024, 768 
AutoMidHandle True
Global fpb:Int = 8
Global Buf:Float[fpb]
Global gscl:Float = 1
Global imgPart = LoadImage("part64.png")
Global rot:Int = 0

error( Pa_Initialize() ,"init")

' set the mix buffer running....
playstreamPA(astream,myCallback)	

Global avgBuf:Double        

'---Set the seed of the RNG
SeedRnd MilliSecs()          

'---create a timer
CreateTimer 30
gotime = 0
While WaitEvent()                

	Select EventID()                    
	
		Case EVENT_TIMERTICK 
	        avgBuf = 0
			Cls
			For Local n:Int=0 To fpb -1
	            SetColor(0,127,0)          
	            SetRotation 0
	            SetScale 1,1
	            SetAlpha 0.7
	            SetBlend ALPHABLEND                                         
				SetColor(255,0,0)  
				If gotime = 0 Then 
					TParticle.Create(512+Abs(Buf[n])*Cos(n*45+rot), 384+Abs(Buf[n])*Sin(n*45+rot), Rand(2,8)*4, 255,127,127, 0.9, 8, imgPart, Rand(0,3))     
				                                                                                                                                                
					'TParticle.Create(512+(Buf[n]), 52+728*n/fpb, Rand(1,16)*4, 255,127,127, 0.9, 4, imgPart, Rand(0,3))     
				EndIf
				avgBuf:+Abs(Buf[n])
			Next
			avgBuf = avgBuf/fpb
			TParticle.Create(0,0,avgBuf, 63,63, 255, 0.5, 4, imgPart, 1)   
			TParticle.Create(0,768,avgBuf, 63,63, 255, 0.5, 4, imgPart, 1)    
			TParticle.Create(1024,0,avgBuf, 63,63, 255, 0.5, 4, imgPart, 1)    
			TParticle.Create(1024,768,avgBuf, 63,63, 255, 0.5, 4, imgPart, 1)     
			'DrawText String(avgBuf), 0, 100
			'SetColor 0,0,avgBuf
			'SetClsColor 0,0,avgBuf
			TParticle.DrawAll
			Flip   
			TParticle.UpdateAll 
			rot :+ 2    
			gotime:+1
			If gotime = 2 gotime = 0          
			
		Case EVENT_KEYDOWN
			If EventData() = KEY_ESCAPE   
					End	
			End If      
			 
	End Select


Wend


Pa_CloseStream(astream)

End



Function myCallback:Int( inputBuffer:Float Ptr, outputBuffer:Short Ptr,..
                           framesPerBuffer:Int,outTime:Double, userData:Byte Ptr )
	For Local n:Int=0 To fpb-1
		Buf[n]=inputBuffer[0]*1024
		inputbuffer:+1  
	Next
EndFunction

                                                                                                                        


Function error(e:Int,s:String)
	If e=0 Then Return
	Pa_Terminate()
	Print "error "+e+" in "+s
	Print "message "+String.FromCString(Pa_GetErrorText(e))
	End
EndFunction	


Function playstreamPA(Stream:Byte Ptr Var,callback:Byte Ptr)

	error(.. 
	Pa_OpenStream( VarPtr Stream,Pa_GetDefaultOutputDeviceID(),1,paFloat32,Null,..
						paNoDevice,0,paInt8,Null,44100,fpb,0,0,..
			              			Callback,Null )..
	,"open stream")	
	
	error( Pa_StartStream( Stream ),"start stream" )
	Print "playing"



EndFunction


hard to tell because i dont have particles.bmx but frames per buffer is WAY to small I'd say 256 is the *smallest* it wants to be...

Chris C: with the code you posted it's drawn the audio spectrum of the input from the microphone?

or buf[] simply contains 256 samples distribuited in time?

the last 256 samples collected...

44100hz/256 is approx 3.5 times a frame... however you can't tell 2/3 of the scope is missing each frame beacuse of the speed and persistance of vision

So, after have read the buffer the first time, when i read it again, if it was updated, i find all new values?

PS because of the audio input, do anyone of you know how to do FFTs?

This "Unhandled Exception:GC clrMemBit: membit not set!" is something I came across when I finally got FMOD streams working. As I understand it, basically, BlitzMax's garbage collector means that all streaming audio is impossible because the GC won't allow a separate thread/callback function thing going on for long before throwing a fit and killing your app. It's the same reason why requests for an official thread library are always shot down.

I'd love to be proven wrong.

@denzilquixode what you discribe is a symptom of spending too long in an iterupt callback, streaming audio simultainously works just fine, providing you do it properly...

@SpLinux
when i read it again, if it was updated, i find all new values?
err arent you answering your own question here, if its been updated would you expect to find old values?

Sorry, how do I know what the maximum length I can spend is?

@ Chris C: ok.

i'm trying to program a speech recognition engine for Linux.
this program it's very useful, bacause now i can get live data.

but i have some questions:
1)so every time it's updated buf[] contains an audio sample @ 44000hz long 256 samples, right?
2)i would be very happy if someone would like to program this engine with me, help is welcome.
3)i've asked if someone know hot to code ffts with BMax, so i wrote this question again.

@denzilquixode sorry I dont understand, if you mean max buffer size, I guess as large as you like, but to be honest I dont know, experimentation will soon tell you...

@splinux
1 yes, but it might have been updated several times b4 you get to it in you main code, you might want to implement a ring of buffers, or make the buffer large enough so that it contains more samples than 1 vertical blank
2 email me
3 I've not done this, but I'm sure it cant be too hard

You said "spending too long in an iterupt callback" is the problem. How do I know what "too long" is and keep below it?

i'll contact you via mail.

basically do as little as possible in the callback, if you notice inconsistant behaviour from the debugger or the program quits unexpectidly then you are probably spending to long in the callback -or- you have a dangling pointer

Can someone package the module and source and send it over to ed@... it would be greatly appreciated :). My current machine doesnt have net access, this machine doesnt have max on it.

Fond the problem I had, you must not build with debug enabled or it will crash!