Game Development Community

dev|Pro Game Development Curriculum

T3D RTT and Audio Capture

by Demolishun · 03/03/2013 (9:31 pm) · 14 comments

Note:
This resource is a combination of a couple of things. To show how to do simple RTT and to show how to capture audio data (in Windows Vista and up). You can pick and choose what you want out of this resource. Also, this should be considered a WIP or a living resource. I wanted to get this into people's hands so they can play/use pieces or all of it.

Background:
I was looking for a way to have the engine respond to external audio sources. I wanted to build something similar to www.vsxu.com/. The idea was to make it so I can put some music on and the source can be from anything. That way T3D does not need to know how to play the media and it can be an internet radio station, youtube, audio player, midi player, movie player, microphone, anything.

I found out that in Windows Vista and above there is an interface that allows you to capture audio from the sound output device (before it actually amplifies this sound, although the volume from each program can amplify the sound). So I took the approach of using loopback capture. So yes the loopback capture portion is Windows Vista and above specific. However, there are ways to do this in Windows XP, but the API is different. It is a similar story with Linux and I am guessing Mac OSX. So if someone needs those platforms please research and share your findings with this resource.

Then I needed to be able to isolate frequencies. So my first inclination was to look for FFT code. I have no idea how to write FFT equations. I know it has to do with wavelet theory and is related to s domain math. I took some in college, but has since been flushed. I found a great little library as a consequence: KISSFFT. It is not the fastest FFT library available, but it is simple, small, and has a BSD license. I also see that the author is active on Stack Overflow and helps people use the lib. There is another equation I will use down the road that is actually better at isolating specific frequencies. I think it is a Gabor Wavelet based equation. So that takes care of detecting the frequencies bands using FFT.

I did some testing early on and found I can use script to manipulate many objects. However, I wanted to create data that C++ objects could use so the reaction to the music could be faster. So that prompted me to explore doing RTT (render to texture) and making objects that have generic access to the raw and converted data. This all had to be done fast without slowing down the main loop of the game. So all capture and conversion are done in a separate thread. That way the main app only has to deal with reacting to the data. I also constructed the processing objects to be able to get their data from other sources besides the loopback capture. This way if someone wanted to create a different data capturing object they can. Something like a capture object that gets the audio stream for a specific character(s). That way you can tie the moving of their lips or mouth to do lip syncing to a speech sound byte. That would be just one example of how this can be expanded.

How it works:

  1. A loopback capture object runs in another thread and grabs audio data continuously. The starting and stopping of this object is controlled through script commands.
  2. Audio processing objects: LoopBackObject and FFTObject are created as SimObjects in script. These are added to the processing queue by using a script command.
  3. When I processing object is in the processing queue they are accessed every 100mS (can be tweaked by a #define statement). When they are accessed their buffers are locked using thread locking mechanisms. The data from the loop back capture will be copied to the objects and the object will perform any data processing on the newly acquired data. For the LoopBackObject there is no processing, and for FFTObject it does a FFT algorithm. I have created and run 500 FFTObjects at once all in the queue. I was still able to interract with the engine. Although I could imagine doing that much processing could slow down the engine. YMMV
  4. Data is extracted from the objects using a preset function calls defined in the LoopBackObject. Each object based upon LoopBackObject must create their own version of the getProcessedOutput function. This is a C++ function that can be used anywhere you need the data. The raw audio is retrieved using getAudioOutput. Both function return a Vector(F32). I need to add a couple of console functions to get this data. Right now it is recommended to only get raw audio from a LoopBackObject and not an FFTObject. The reason is the FTTObject "tweaks" its internal data using a "windowing" (math term) function.

Usage:

  1. renderRTTExample.h/.cpp are not required for the audio loopback. It is an example of RTT.
  2. processedMaterial.cpp and gfxTextureHandle.cpp are changes to the engine files of the same name. The Generic enabling of texture targets describes the same engine file changes. So these files are not needed if you already made those changes.
  3. audioTextureObject.h/.cpp are the SceneObject based objects that visualize the audio data provided by the LoopBackObject for now.
  4. loopbackAudio.h/.cpp are the loop back capture object and processing objects: LoopBackObject and FTTObject. It might be a good idea to separate the processing objects into a separate file so it is not dependent upon the Windows only code used for the loop back capture.
  5. test.cs is a script file for testing using the code. It assumes the existence of certain gui elements. You can pick the names of those elements out of the script code. It should be fairly self explanatory.
  6. usage.c shows how to setup the render to texture targets using my persistent NamedTexTargetObject SimObject. Make sure you use two values in texDims (not texSize) attribute (ie texDims = "1024 1024";) I will fix that, but I inadvertently used an older usage.cs file. The texSize attribute is not needed. Put the NamedTexTargetObject somewhere that it will get created before anything in the mission/gui objects that use it. It can be created at startup, just remember that it does create a texture of the size specified in texDims. So it can eat up memory on the graphics card if you create too many. Use wisely. This object basically solves the issue for creating a named texture target from being loaded in the wrong order if the texture was defined in a SceneObject. The NamedTexTargetObject is used by the AudioTextureObject.

Code:
Links to code:
Loopback Audio Version 1.0
Background render example

RTT required engine changes:
Generic enabling of texture targets

Why is this resource so crappy? How do I use this?
I am being purposely vague how to use this. It WILL require skill in C++ and T3D codebase to install. When I get some time I may add some data to show how to install this code and use each piece. However, I know that a large number of Torque developers are very capable of doing this. I just am behind on my other work. This started out last year as a simple feature and has grown like a vine. It also required me to take the time to learn a significant number of things in the engine, in the OS (threading), and in Windows. Basically this "simple" feature took me 4 or 5 months to piece together. Gah! Has it really been that long?! Anyway, I promised to get this out there so people can get their hands on pieces of this. That is the main reason I am posting now.

So, if you have question about installation please post. Then I can construct a step by step from where people had issues.

Why give this away?
Simply put: Because the GG community is awesome. They have made it possible to use a very good engine to make things I simply don't have time to build from scratch. Everyone here is so eager to help so any code I contribute back just makes this community that much better. I know that YOU are like me and have only so much time in the day. I also know that nothing of significance is done by individuals. So I want to enable you and your dreams. In return I ask that you just be your "awesome self".

Thanks for being so great!

About the author

I love programming, I love programming things that go click, whirr, boom. For organized T3D Links visit: http://demolishun.com/?page_id=67


#1
03/04/2013 (4:49 am)
Was hoping to see this, i might need some advice or assistance later
anyways great resource!
#2
03/04/2013 (5:45 am)
Frank, very cool resource!

Ron
#3
03/04/2013 (2:16 pm)
Added a Usage section.

Edit:
Added some more usage script info and added an example blend file for an object that sets the material to a specific name. This object is not needed though. You really just need a material with the correct tex target and an object that can accept a material, or in the case of some objects, just the tex target name. Of course when referencing a text target it must be #<targetname>. In the script examples this would be #hellotex. Here is the results of an object using this:
new SpotLight(audiolightemitter) {
      range = "25";
      innerAngle = "75";
      outerAngle = "80";
      isEnabled = "1";
      color = "1 1 1 1";
      brightness = "100";
      castShadows = "1";
      priority = "1";
      animate = "0";
      animationType = "SpinLightAnim";
      animationPeriod = "1";
      animationPhase = "1";
      flareScale = "1";
      attenuationRatio = "0 1 1";
      shadowType = "Spot";
      cookie = "#hellotex";
      texSize = "1024";
      overDarkFactor = "2000 1000 500 100";
      shadowDistance = "400";
      shadowSoftness = "0.15";
      numSplits = "1";
      logWeight = "0.91";
      fadeStartDistance = "0";
      lastSplitTerrainOnly = "0";
      representedInLightmap = "1";
      shadowDarkenColor = "0 3.04362e-028 2.48375e-031 1";
      includeLightmappedGeometryInShadow = "1";
      position = "-3.43653 -16.0717 11.1931";
      rotation = "0.584348 0.57382 -0.573819 119.4";
      mountPID = "2bb5cce8-767e-11e2-b324-f4633c2d6300";
      mountNode = "-1";
      mountPos = "0 0 0";
      mountRot = "0.584348 0.57382 -0.573819 119.4";
      canSave = "1";
      canSaveDynamicFields = "1";
      persistentId = "2bb5cce9-767e-11e2-b324-f4633c2d6300";
   };
   new Item() {
      static = "1";
      rotate = "1";
      isAIControlled = "0";
      dataBlock = "SpinBlockData";
      position = "-3.43653 -16.0717 11.1931";
      rotation = "1 0 0 0";
      scale = "1 1 1";
      canSave = "1";
      canSaveDynamicFields = "1";
      persistentId = "2bb5cce8-767e-11e2-b324-f4633c2d6300";
   };
BTW the Item has the spotlight mounted on it so it can spin it facing down. Not really needed, but cool to look at.
#4
05/24/2013 (4:56 pm)
Im having trouble using this.Im trying to get a simple RTT example working.
I put this script in GameConnection::onClientEnterGame:
singleton NamedTexTargetObject(MyTexTargetObject) 
   {
      targetName = "hellotex";
      texDims = "1024 1024";
   };
   
   new RenderRTTExample(rtt)
   {
      textureTargetName="hellotex";
   };

I get:
NamedTexTargetObject::onAdd - Texture target registered: hellotex
RenderRTTExample::updateStuff(): Ah man, your texture registration crashed and burned!

Can you tell me what script should i put and where to observe how RenderRTTExample works.It should render a colored box i presume.Also do you think rendering to texture which is used in terrain rendering is possible?
Thanks

Edit. I got it working with
new RenderRTTExample(rtt)
   {
      textureTargetName="hellotex";
   };
   
   singleton Material(testcube_Hello)
   {
      mapTo = "Hello";
      diffuseMap[0] = "#hellotex";
   };
and then applying testcube_Hello matterial to some object in the scene. You just gotta make sure that RenderRTTExample is in your camera view, so its render functions are called.
Rendering to terrain is not as straightforward unfortunately.I changed the above code to this.

new RenderRTTExample(rtt)
   {
      textureTargetName="hellotex";
   };
   
   new TerrainMaterial(rtt)
    {
    diffuseMap = "#hellotex";

    };

And i apply this terrain material to terrain in the editor, it renders one frame but wont animate.When i paint with this material, or simply reapply it again, it will update the frame.And the saddest part is: when i switch back to game mode, it wont even show the same frame as in editor, but just black color.
Anybody knows how to fix this? Maybe theres some function i can call to force terrain update its material?
#5
05/25/2013 (5:53 pm)
Make sure the singleton is created only once. Otherwise you will get message spam in your console.

I have used this with render to the ground plane object, but I have not experimented with the terrain. Also, did you update the material fixes in the engine (Required Engine Changes)?
#6
05/26/2013 (3:21 am)
If by singleton you mean NamedTexTargetObject, then i dont create it at all. I dont understand why i need it. Yes i implemented egine changes.

The problem with terrain i think is that it takes its materials and creates a texture based on them, which it uses to render itself. Textures that are passed to materials are not used in the rendering directly.I wonder if there is a way to render to texture that terrain actually uses and not one of its materials.

#7
05/26/2013 (11:19 pm)
NamedTexTargetObject is NOT optional. If you don't use it the resource won't work. It is how you tell the engine that you are using a dynamic texture. Without it you will get nothing. I am also guessing that it will work fine because every other material I have testing has worked fine. The examples provided with the resource show the script needed to make this work.
#8
08/19/2013 (5:15 pm)
I got the strangest bug I hooked up the RTT example and got the material setup and on a cube but it only seems to update live when I'm looking like 90 degrees up at the sky. I don't get why it does that but only when I'm pointing the camera at the sky. I can even get under the cube and look up at the sky and it updates while I'm looking at it.
#9
08/19/2013 (9:12 pm)
It's that terrain glitch you were talking about any fixes for it yet?
#10
08/19/2013 (9:20 pm)
It doesn't appear to be the terrain because if I stick the camera under the terrain and look at the sky it still updates with most of the terrain in the camerea.
#11
08/21/2013 (3:50 pm)
I have not addressed the terrain at all. I did not see it being important as I think the reason the terrain materials work the way they do if for speed.

As far as a cube material only updating when looking upward that is strange. It sounds like a network scoping issue. Did you implement the resource exactly? Did I not document something so as to make it fail? What version of T3D are you using? I have only tested this on T3D MIT 1.2.
#12
08/21/2013 (11:43 pm)
Oh that might be it I'm running T3D MIT 3.0 so there might be some serious changes since that time. I'll check out the scoping issue though. It wouldn't make much sense unless its trying to update with the skybox or something way up there.
#13
03/17/2014 (6:29 pm)
I know i'm going to be flamed for asking this but... How do I add this to my project. Where do I need to put the files? I just need a noobs guide on how to include this
#14
03/17/2014 (9:13 pm)
Most of the .cpp/.h files are added to the 'source' directory that sits at the same level as my game directory in my 'project'. These need to be added to the project using MSVC.

The gfxTextureHandle.cpp and processedMaterial.cpp files change the functionality of the stock files they replace. Make sure you do a diff between my files and the files in the version of the engine you are using. That way you don't overwrite changes or fixes that may be in those files.

usage.cs needs to get loaded somewhere by engine before you need to use them. I load mine at startup sometime. I don't remember exactly where.

test.cs would be an example of how to use the code to affect script values for use however you want. Also note that all of this code should be considered very raw. To really use this for anything but simple gui widget manipulation you are going to want to possibly write your own C++ classes that get the data from the objects themselves.

It has been a while since I even looked at this code so it may require some experimenting to get it working with the new versions of T3D. Also, as is this code kind of locks you into using it for Windows. Be aware of that.

Honestly I think the best part of this code is showing how to write code that works with threads and simobjects in a non main thread blocking scheme.

Oh, at GG we almost never flame. Unless it is SPAM or something. Even then...