1 #include <stdio.h>
2 #include <string.h>
3 #include <assert.h>
4 
5 #ifdef _WIN32
6 #include <direct.h>
7 #define getcwd _getcwd
8 #else
9 #include <unistd.h>
10 #endif
11 
12 #include "ShaderEditor.h"
13 #include "Renderer.h"
14 #include "FFT.h"
15 #include "MIDI.h"
16 #include "Timer.h"
17 #include "Misc.h"
18 #include "UniConversion.h"
19 #include "jsonxx.h"
20 #include "Capture.h"
21 #include "SetupDialog.h"
22 
ParseColor(const std::string & color)23 unsigned int ParseColor(const std::string& color) {
24   if (color.size() < 6 || color.size() > 8) return 0xFFFFFFFF;
25   if (color.size() == 6)
26   {
27     std::string text = "0x" + color;
28     unsigned int v = std::stoul(text, 0, 16);
29     return (0xFF000000 | ((v & 0xFF0000) >> 16) | (v & 0x00FF00) | ((v & 0x0000FF) << 16));
30   }
31   else
32   {
33     std::string text = "0x" + color;
34     unsigned int v = std::stoul(text, 0, 16);
35     return ((v & 0xFF000000) | ((v & 0x00FF0000) >> 16) | (v & 0x0000FF00) | ((v & 0x000000FF) << 16));
36   }
37 }
38 
ReplaceTokens(std::string & sDefShader,const char * sTokenBegin,const char * sTokenName,const char * sTokenEnd,std::vector<std::string> & tokens)39 void ReplaceTokens( std::string &sDefShader, const char * sTokenBegin, const char * sTokenName, const char * sTokenEnd, std::vector<std::string> &tokens )
40 {
41   if (sDefShader.find(sTokenBegin) != std::string::npos
42     && sDefShader.find(sTokenName) != std::string::npos
43     && sDefShader.find(sTokenEnd) != std::string::npos
44     && sDefShader.find(sTokenBegin) < sDefShader.find(sTokenName)
45     && sDefShader.find(sTokenName) < sDefShader.find(sTokenEnd))
46   {
47     int nTokenStart = (int)(sDefShader.find(sTokenBegin) + strlen(sTokenBegin));
48     std::string sTextureToken = sDefShader.substr( nTokenStart, sDefShader.find(sTokenEnd) - nTokenStart );
49 
50     std::string sFinalShader;
51     sFinalShader = sDefShader.substr( 0, sDefShader.find(sTokenBegin) );
52 
53     //for (std::map<std::string, Renderer::Texture*>::iterator it = tokens.begin(); it != tokens.end(); it++)
54     for (int i=0; i < tokens.size(); i++)
55     {
56       std::string s = sTextureToken;
57       while (s.find(sTokenName) != std::string::npos)
58       {
59         s.replace( s.find(sTokenName), strlen(sTokenName), tokens[i], 0, std::string::npos );
60       }
61       sFinalShader += s;
62     }
63     sFinalShader += sDefShader.substr( sDefShader.find(sTokenEnd) + strlen(sTokenEnd), std::string::npos );
64     sDefShader = sFinalShader;
65   }
66 }
67 
main(int argc,const char * argv[])68 int main( int argc, const char *argv[] )
69 {
70   Misc::PlatformStartup();
71 
72   const char * configFile = "config.json";
73   if ( argc > 1 )
74   {
75     configFile = argv[ 1 ];
76     printf( "Loading config file '%s'...\n", configFile );
77   }
78   else
79   {
80     char configPath[ 256 ] = { 0 };
81     if ( getcwd( configPath, 255 ) )
82     {
83       printf( "Looking for config.json in '%s'...\n", configPath );
84     }
85   }
86 
87   jsonxx::Object options;
88   FILE * fConf = fopen( configFile, "rb" );
89   if ( fConf )
90   {
91     printf( "Config file found, parsing...\n" );
92 
93     char szConfig[ 65535 ];
94     memset( szConfig, 0, 65535 );
95     fread( szConfig, 1, 65535, fConf );
96     fclose( fConf );
97 
98     options.parse( szConfig );
99   }
100 
101   FFT::Create();
102 
103   bool skipSetupDialog = false;
104   if ( options.has<jsonxx::Boolean>( "skipSetupDialog" ) )
105     skipSetupDialog = options.get<jsonxx::Boolean>( "skipSetupDialog" );
106 
107   SetupDialog::SETTINGS settings;
108   settings.sFFT.bUseRecordingDevice = true;
109   settings.sFFT.pDeviceID = NULL;
110   if ( options.has<jsonxx::Object>( "audio" ) )
111   {
112     if ( options.get<jsonxx::Object>( "audio" ).has<jsonxx::Boolean>( "useInput" ) )
113       settings.sFFT.bUseRecordingDevice = options.get<jsonxx::Object>( "audio" ).get<jsonxx::Boolean>( "useInput" );
114   }
115 
116   settings.sRenderer.bVsync = false;
117 #ifdef _DEBUG
118   settings.sRenderer.nWidth = 1280;
119   settings.sRenderer.nHeight = 720;
120   settings.sRenderer.windowMode = RENDERER_WINDOWMODE_WINDOWED;
121 #else
122   settings.sRenderer.nWidth = 1920;
123   settings.sRenderer.nHeight = 1080;
124   settings.sRenderer.windowMode = RENDERER_WINDOWMODE_FULLSCREEN;
125 
126   if ( options.has<jsonxx::Object>( "window" ) )
127   {
128     if ( options.get<jsonxx::Object>( "window" ).has<jsonxx::Number>( "width" ) )
129       settings.sRenderer.nWidth = options.get<jsonxx::Object>( "window" ).get<jsonxx::Number>( "width" );
130     if ( options.get<jsonxx::Object>( "window" ).has<jsonxx::Number>( "height" ) )
131       settings.sRenderer.nHeight = options.get<jsonxx::Object>( "window" ).get<jsonxx::Number>( "height" );
132     if ( options.get<jsonxx::Object>( "window" ).has<jsonxx::Boolean>( "fullscreen" ) )
133       settings.sRenderer.windowMode = options.get<jsonxx::Object>( "window" ).get<jsonxx::Boolean>( "fullscreen" ) ? RENDERER_WINDOWMODE_FULLSCREEN : RENDERER_WINDOWMODE_WINDOWED;
134   }
135   if ( !skipSetupDialog )
136   {
137     if ( !SetupDialog::Open( &settings ) )
138     {
139       return -1;
140     }
141   }
142 #endif
143 
144   if (!Renderer::Open( &settings.sRenderer ))
145   {
146     printf("Renderer::Open failed\n");
147     return -1;
148   }
149 
150   if (!FFT::Open( &settings.sFFT ))
151   {
152     printf("FFT::Open() failed, continuing anyway...\n");
153     //return -1;
154   }
155 
156   if (!MIDI::Open())
157   {
158     printf("MIDI::Open() failed, continuing anyway...\n");
159     //return -1;
160   }
161 
162   std::map<std::string,Renderer::Texture*> textures;
163   std::map<int,std::string> midiRoutes;
164 
165   const char * szDefaultFontPath = Misc::GetDefaultFontPath();
166 
167   SHADEREDITOR_OPTIONS editorOptions;
168   editorOptions.nFontSize = 16;
169   if ( !szDefaultFontPath )
170   {
171     printf( "Misc::GetDefaultFontPath couldn't find ANY default fonts!\n" );
172   }
173   else
174   {
175     editorOptions.sFontPath = szDefaultFontPath;
176   }
177   editorOptions.nOpacity = 0xC0;
178   editorOptions.bUseSpacesForTabs = true;
179   editorOptions.nTabSize = 2;
180   editorOptions.bVisibleWhitespace = false;
181   editorOptions.eAutoIndent = aitSmart;
182 
183   int nDebugOutputHeight = 200;
184   int nTexPreviewWidth = 64;
185   float fFFTSmoothingFactor = 0.9f; // higher value, smoother FFT
186   float fFFTSlightSmoothingFactor = 0.6f; // higher value, smoother FFT
187   float fScrollXFactor = 1.0f;
188   float fScrollYFactor = 1.0f;
189 
190   std::string sPostExitCmd;
191 
192   if (!options.empty())
193   {
194     if (options.has<jsonxx::Object>("rendering"))
195     {
196       if (options.get<jsonxx::Object>("rendering").has<jsonxx::Number>("fftSmoothFactor"))
197         fFFTSmoothingFactor = options.get<jsonxx::Object>("rendering").get<jsonxx::Number>("fftSmoothFactor");
198       if (options.get<jsonxx::Object>("rendering").has<jsonxx::Number>("fftAmplification"))
199         FFT::fAmplification = options.get<jsonxx::Object>("rendering").get<jsonxx::Number>("fftAmplification");
200     }
201 
202     if (options.has<jsonxx::Object>("textures"))
203     {
204       printf("Loading textures...\n");
205       std::map<std::string, jsonxx::Value*> tex = options.get<jsonxx::Object>("textures").kv_map();
206       for (std::map<std::string, jsonxx::Value*>::iterator it = tex.begin(); it != tex.end(); it++)
207       {
208         const char * fn = it->second->string_value_->c_str();
209         printf("* %s...\n",fn);
210         Renderer::Texture * tex = Renderer::CreateRGBA8TextureFromFile( fn );
211         if (!tex)
212         {
213           printf("Renderer::CreateRGBA8TextureFromFile(%s) failed\n",fn);
214           return -1;
215         }
216         textures[it->first] = tex;
217       }
218     }
219     if (options.has<jsonxx::Object>("font"))
220     {
221       if (options.get<jsonxx::Object>("font").has<jsonxx::Number>("size"))
222         editorOptions.nFontSize = options.get<jsonxx::Object>("font").get<jsonxx::Number>("size");
223       if (options.get<jsonxx::Object>("font").has<jsonxx::String>("file"))
224       {
225         std::string fontpath = options.get<jsonxx::Object>("font").get<jsonxx::String>("file");
226         if (!Misc::FileExists(fontpath.c_str()))
227         {
228           printf("Font path (%s) is invalid!\n", fontpath.c_str());
229           return -1;
230         }
231         editorOptions.sFontPath = fontpath;
232       }
233     }
234     if (options.has<jsonxx::Object>("gui"))
235     {
236       if (options.get<jsonxx::Object>("gui").has<jsonxx::Number>("outputHeight"))
237         nDebugOutputHeight = options.get<jsonxx::Object>("gui").get<jsonxx::Number>("outputHeight");
238       if (options.get<jsonxx::Object>("gui").has<jsonxx::Number>("texturePreviewWidth"))
239         nTexPreviewWidth = options.get<jsonxx::Object>("gui").get<jsonxx::Number>("texturePreviewWidth");
240       if (options.get<jsonxx::Object>("gui").has<jsonxx::Number>("opacity"))
241         editorOptions.nOpacity = options.get<jsonxx::Object>("gui").get<jsonxx::Number>("opacity");
242       if (options.get<jsonxx::Object>("gui").has<jsonxx::Boolean>("spacesForTabs"))
243         editorOptions.bUseSpacesForTabs = options.get<jsonxx::Object>("gui").get<jsonxx::Boolean>("spacesForTabs");
244       if (options.get<jsonxx::Object>("gui").has<jsonxx::Number>("tabSize"))
245         editorOptions.nTabSize = options.get<jsonxx::Object>("gui").get<jsonxx::Number>("tabSize");
246       if (options.get<jsonxx::Object>("gui").has<jsonxx::Boolean>("visibleWhitespace"))
247         editorOptions.bVisibleWhitespace = options.get<jsonxx::Object>("gui").get<jsonxx::Boolean>("visibleWhitespace");
248       if (options.get<jsonxx::Object>("gui").has<jsonxx::String>("autoIndent"))
249       {
250         std::string autoIndent = options.get<jsonxx::Object>("gui").get<jsonxx::String>("autoIndent");
251         if (autoIndent == "smart") {
252           editorOptions.eAutoIndent = aitSmart;
253         } else if (autoIndent == "preserve") {
254           editorOptions.eAutoIndent = aitPreserve;
255         } else {
256           editorOptions.eAutoIndent = aitNone;
257         }
258       }
259       if (options.get<jsonxx::Object>("gui").has<jsonxx::Number>("scrollXFactor"))
260         fScrollXFactor = options.get<jsonxx::Object>("gui").get<jsonxx::Number>("scrollXFactor");
261       if (options.get<jsonxx::Object>("gui").has<jsonxx::Number>("scrollYFactor"))
262         fScrollYFactor = options.get<jsonxx::Object>("gui").get<jsonxx::Number>("scrollYFactor");
263     }
264     if (options.has<jsonxx::Object>("theme"))
265     {
266       const auto& theme = options.get<jsonxx::Object>("theme");
267       if (theme.has<jsonxx::String>("text"))
268         editorOptions.theme.text = ParseColor(theme.get<jsonxx::String>("text"));
269       if (theme.has<jsonxx::String>("comment"))
270         editorOptions.theme.comment = ParseColor(theme.get<jsonxx::String>("comment"));
271       if (theme.has<jsonxx::String>("number"))
272         editorOptions.theme.number = ParseColor(theme.get<jsonxx::String>("number"));
273       if (theme.has<jsonxx::String>("op"))
274         editorOptions.theme.op = ParseColor(theme.get<jsonxx::String>("op"));
275       if (theme.has<jsonxx::String>("keyword"))
276         editorOptions.theme.keyword = ParseColor(theme.get<jsonxx::String>("keyword"));
277       if (theme.has<jsonxx::String>("type"))
278         editorOptions.theme.type = ParseColor(theme.get<jsonxx::String>("type"));
279       if (theme.has<jsonxx::String>("builtin"))
280         editorOptions.theme.builtin = ParseColor(theme.get<jsonxx::String>("builtin"));
281       if (theme.has<jsonxx::String>("preprocessor"))
282         editorOptions.theme.preprocessor = ParseColor(theme.get<jsonxx::String>("preprocessor"));
283       if (theme.has<jsonxx::String>("selection"))
284         editorOptions.theme.selection = ParseColor(theme.get<jsonxx::String>("selection"));
285       if (theme.has<jsonxx::String>("charBackground")) {
286         editorOptions.theme.bUseCharBackground = true;
287         editorOptions.theme.charBackground = ParseColor(theme.get<jsonxx::String>("charBackground"));
288       }
289     }
290     if (options.has<jsonxx::Object>("midi"))
291     {
292       std::map<std::string, jsonxx::Value*> tex = options.get<jsonxx::Object>("midi").kv_map();
293       for (std::map<std::string, jsonxx::Value*>::iterator it = tex.begin(); it != tex.end(); it++)
294       {
295         midiRoutes[it->second->number_value_] = it->first;
296       }
297     }
298     if (options.has<jsonxx::String>("postExitCmd"))
299     {
300       sPostExitCmd = options.get<jsonxx::String>("postExitCmd");
301     }
302     Capture::LoadSettings( options );
303   }
304   if (!editorOptions.sFontPath.size())
305   {
306     printf("Couldn't find any of the default fonts. Please specify one in config.json\n");
307     return -1;
308   }
309   if (!Capture::Open(settings.sRenderer))
310   {
311     printf("Initializing capture system failed!\n");
312     return 0;
313   }
314 
315   Renderer::Texture * texPreviousFrame = Renderer::CreateRGBA8Texture();
316   Renderer::Texture * texFFT = Renderer::Create1DR32Texture( FFT_SIZE );
317   Renderer::Texture * texFFTSmoothed = Renderer::Create1DR32Texture( FFT_SIZE );
318   Renderer::Texture * texFFTIntegrated = Renderer::Create1DR32Texture( FFT_SIZE );
319 
320   bool shaderInitSuccessful = false;
321   char szShader[65535];
322   char szError[4096];
323   FILE * f = fopen(Renderer::defaultShaderFilename,"rb");
324   if (f)
325   {
326     printf("Loading last shader...\n");
327 
328     memset( szShader, 0, 65535 );
329     fread( szShader, 1, 65535, f );
330     fclose(f);
331     if (Renderer::ReloadShader( szShader, (int)strlen(szShader), szError, 4096 ))
332     {
333       printf("Last shader works fine.\n");
334       shaderInitSuccessful = true;
335     }
336     else {
337       printf("Shader error:\n%s\n", szError);
338     }
339   }
340   if (!shaderInitSuccessful)
341   {
342     printf("No valid last shader found, falling back to default...\n");
343 
344     std::string sDefShader = Renderer::defaultShader;
345 
346     std::vector<std::string> tokens;
347     for (std::map<std::string, Renderer::Texture*>::iterator it = textures.begin(); it != textures.end(); it++)
348       tokens.push_back(it->first);
349     ReplaceTokens(sDefShader, "{%textures:begin%}", "{%textures:name%}", "{%textures:end%}", tokens);
350 
351     tokens.clear();
352     for (std::map<int,std::string>::iterator it = midiRoutes.begin(); it != midiRoutes.end(); it++)
353       tokens.push_back(it->second);
354     ReplaceTokens(sDefShader, "{%midi:begin%}", "{%midi:name%}", "{%midi:end%}", tokens);
355 
356     strncpy( szShader, sDefShader.c_str(), 65535 );
357     if (!Renderer::ReloadShader( szShader, (int)strlen(szShader), szError, 4096 ))
358     {
359       printf("Default shader compile failed:\n");
360       puts(szError);
361       assert(0);
362     }
363   }
364 
365   Misc::InitKeymaps();
366 
367 #ifdef SCI_LEXER
368   Scintilla_LinkLexers();
369 #endif
370   Scintilla::Surface * surface = Scintilla::Surface::Allocate( SC_TECHNOLOGY_DEFAULT );
371   surface->Init( NULL );
372 
373   int nMargin = 20;
374 
375   bool bTexPreviewVisible = true;
376 
377   editorOptions.rect = Scintilla::PRectangle( nMargin, nMargin, settings.sRenderer.nWidth - nMargin - nTexPreviewWidth - nMargin, settings.sRenderer.nHeight - nMargin * 2 - nDebugOutputHeight );
378   ShaderEditor mShaderEditor( surface );
379   mShaderEditor.Initialise( editorOptions );
380   mShaderEditor.SetText( szShader );
381 
382   editorOptions.rect = Scintilla::PRectangle( nMargin, settings.sRenderer.nHeight - nMargin - nDebugOutputHeight, settings.sRenderer.nWidth - nMargin - nTexPreviewWidth - nMargin, settings.sRenderer.nHeight - nMargin );
383   ShaderEditor mDebugOutput( surface );
384   mDebugOutput.Initialise( editorOptions );
385   mDebugOutput.SetText( "" );
386   mDebugOutput.SetReadOnly(true);
387 
388   static float fftData[FFT_SIZE];
389   memset(fftData, 0, sizeof(float) * FFT_SIZE);
390   static float fftDataSmoothed[FFT_SIZE];
391   memset(fftDataSmoothed, 0, sizeof(float) * FFT_SIZE);
392 
393 
394   static float fftDataSlightlySmoothed[FFT_SIZE];
395   memset(fftDataSlightlySmoothed, 0, sizeof(float) * FFT_SIZE);
396   static float fftDataIntegrated[FFT_SIZE];
397   memset(fftDataIntegrated, 0, sizeof(float) * FFT_SIZE);
398 
399   bool bShowGui = true;
400   Timer::Start();
401   float fNextTick = 0.1f;
402   float fLastTimeMS = Timer::GetTime();
403   while (!Renderer::WantsToQuit())
404   {
405     bool newShader = false;
406     float time = Timer::GetTime() / 1000.0; // seconds
407     Renderer::StartFrame();
408 
409     for(int i=0; i<Renderer::mouseEventBufferCount; i++)
410     {
411       if (bShowGui)
412       {
413         switch (Renderer::mouseEventBuffer[i].eventType)
414         {
415           case Renderer::MOUSEEVENTTYPE_MOVE:
416             mShaderEditor.ButtonMovePublic( Scintilla::Point( Renderer::mouseEventBuffer[i].x, Renderer::mouseEventBuffer[i].y ) );
417             break;
418           case Renderer::MOUSEEVENTTYPE_DOWN:
419             mShaderEditor.ButtonDown( Scintilla::Point( Renderer::mouseEventBuffer[i].x, Renderer::mouseEventBuffer[i].y ), time * 1000, false, false, false );
420             break;
421           case Renderer::MOUSEEVENTTYPE_UP:
422             mShaderEditor.ButtonUp( Scintilla::Point( Renderer::mouseEventBuffer[i].x, Renderer::mouseEventBuffer[i].y ), time * 1000, false );
423             break;
424           case Renderer::MOUSEEVENTTYPE_SCROLL:
425             mShaderEditor.WndProc( SCI_LINESCROLL, (int)(-Renderer::mouseEventBuffer[i].x * fScrollXFactor), (int)(-Renderer::mouseEventBuffer[i].y * fScrollYFactor));
426             break;
427         }
428       }
429     }
430     Renderer::mouseEventBufferCount = 0;
431 
432     for(int i=0; i<Renderer::keyEventBufferCount; i++)
433     {
434       if (Renderer::keyEventBuffer[i].scanCode == 283) // F2
435       {
436         if (bTexPreviewVisible)
437         {
438           mShaderEditor.SetPosition( Scintilla::PRectangle( nMargin, nMargin, settings.sRenderer.nWidth - nMargin, settings.sRenderer.nHeight - nMargin * 2 - nDebugOutputHeight ) );
439           mDebugOutput .SetPosition( Scintilla::PRectangle( nMargin, settings.sRenderer.nHeight - nMargin - nDebugOutputHeight, settings.sRenderer.nWidth - nMargin, settings.sRenderer.nHeight - nMargin ) );
440           bTexPreviewVisible = false;
441         }
442         else
443         {
444           mShaderEditor.SetPosition( Scintilla::PRectangle( nMargin, nMargin, settings.sRenderer.nWidth - nMargin - nTexPreviewWidth - nMargin, settings.sRenderer.nHeight - nMargin * 2 - nDebugOutputHeight ) );
445           mDebugOutput .SetPosition( Scintilla::PRectangle( nMargin, settings.sRenderer.nHeight - nMargin - nDebugOutputHeight, settings.sRenderer.nWidth - nMargin - nTexPreviewWidth - nMargin, settings.sRenderer.nHeight - nMargin ) );
446           bTexPreviewVisible = true;
447         }
448       }
449       else if (Renderer::keyEventBuffer[i].scanCode == 286 || (Renderer::keyEventBuffer[i].ctrl && Renderer::keyEventBuffer[i].scanCode == 'r')) // F5
450       {
451         mShaderEditor.GetText(szShader,65535);
452         if (Renderer::ReloadShader( szShader, (int)strlen(szShader), szError, 4096 ))
453         {
454           // Shader compilation successful; we set a flag to save if the frame render was successful
455           // (If there is a driver crash, don't save.)
456           newShader = true;
457         }
458         else
459         {
460           mDebugOutput.SetText( szError );
461         }
462       }
463       else if (Renderer::keyEventBuffer[i].scanCode == 292 || (Renderer::keyEventBuffer[i].ctrl && Renderer::keyEventBuffer[i].scanCode == 'f')) // F11 or Ctrl/Cmd-f
464       {
465         bShowGui = !bShowGui;
466       }
467       else if (bShowGui)
468       {
469         bool consumed = false;
470         if (Renderer::keyEventBuffer[i].scanCode)
471         {
472           mShaderEditor.KeyDown(
473             iswalpha(Renderer::keyEventBuffer[i].scanCode) ? towupper(Renderer::keyEventBuffer[i].scanCode) : Renderer::keyEventBuffer[i].scanCode,
474             Renderer::keyEventBuffer[i].shift,
475             Renderer::keyEventBuffer[i].ctrl,
476             Renderer::keyEventBuffer[i].alt,
477             &consumed);
478         }
479         if (!consumed && Renderer::keyEventBuffer[i].character)
480         {
481           char    utf8[5] = {0,0,0,0,0};
482           wchar_t utf16[2] = {Renderer::keyEventBuffer[i].character, 0};
483           Scintilla::UTF8FromUTF16(utf16, 1, utf8, 4 * sizeof(char));
484           mShaderEditor.AddCharUTF(utf8, (unsigned int)strlen(utf8));
485         }
486 
487       }
488     }
489     Renderer::keyEventBufferCount = 0;
490 
491     Renderer::SetShaderConstant( "fGlobalTime", time );
492     Renderer::SetShaderConstant( "v2Resolution", settings.sRenderer.nWidth, settings.sRenderer.nHeight );
493 
494     float fTime = Timer::GetTime();
495     Renderer::SetShaderConstant( "fFrameTime", ( fTime - fLastTimeMS ) / 1000.0f );
496     fLastTimeMS = fTime;
497 
498     for (std::map<int,std::string>::iterator it = midiRoutes.begin(); it != midiRoutes.end(); it++)
499     {
500       Renderer::SetShaderConstant( it->second.c_str(), MIDI::GetCCValue( it->first ) );
501     }
502 
503 
504     if (FFT::GetFFT(fftData))
505     {
506       Renderer::UpdateR32Texture( texFFT, fftData );
507 
508       const static float maxIntegralValue = 1024.0f;
509       for ( int i = 0; i < FFT_SIZE; i++ )
510       {
511         fftDataSmoothed[i] = fftDataSmoothed[i] * fFFTSmoothingFactor + (1 - fFFTSmoothingFactor) * fftData[i];
512 
513         fftDataSlightlySmoothed[i] = fftDataSlightlySmoothed[i] * fFFTSlightSmoothingFactor + (1 - fFFTSlightSmoothingFactor) * fftData[i];
514         fftDataIntegrated[i] = fftDataIntegrated[i] + fftDataSlightlySmoothed[i];
515         if (fftDataIntegrated[i] > maxIntegralValue) {
516           fftDataIntegrated[i] -= maxIntegralValue;
517         }
518       }
519 
520       Renderer::UpdateR32Texture( texFFTSmoothed, fftDataSmoothed );
521       Renderer::UpdateR32Texture( texFFTIntegrated, fftDataIntegrated );
522     }
523 
524     Renderer::SetShaderTexture( "texFFT", texFFT );
525     Renderer::SetShaderTexture( "texFFTSmoothed", texFFTSmoothed );
526     Renderer::SetShaderTexture( "texFFTIntegrated", texFFTIntegrated );
527     Renderer::SetShaderTexture( "texPreviousFrame", texPreviousFrame );
528 
529     for (std::map<std::string, Renderer::Texture*>::iterator it = textures.begin(); it != textures.end(); it++)
530     {
531       Renderer::SetShaderTexture( it->first.c_str(), it->second );
532     }
533 
534     Renderer::RenderFullscreenQuad();
535 
536     Renderer::CopyBackbufferToTexture( texPreviousFrame );
537 
538     Renderer::StartTextRendering();
539 
540     if (bShowGui)
541     {
542       if (time > fNextTick)
543       {
544         mShaderEditor.Tick();
545         mDebugOutput.Tick();
546         fNextTick = time + 0.1;
547       }
548 
549       mShaderEditor.Paint();
550       mDebugOutput.Paint();
551 
552       Renderer::SetTextRenderingViewport( Scintilla::PRectangle(0,0,Renderer::nWidth,Renderer::nHeight) );
553 
554       if (bTexPreviewVisible)
555       {
556         int y1 = nMargin;
557         int x1 = settings.sRenderer.nWidth - nMargin - nTexPreviewWidth;
558         int x2 = settings.sRenderer.nWidth - nMargin;
559         for (std::map<std::string, Renderer::Texture*>::iterator it = textures.begin(); it != textures.end(); it++)
560         {
561           int y2 = y1 + nTexPreviewWidth * (it->second->height / (float)it->second->width);
562           Renderer::BindTexture( it->second );
563           Renderer::RenderQuad(
564             Renderer::Vertex( x1, y1, 0xccFFFFFF, 0.0, 0.0 ),
565             Renderer::Vertex( x2, y1, 0xccFFFFFF, 1.0, 0.0 ),
566             Renderer::Vertex( x2, y2, 0xccFFFFFF, 1.0, 1.0 ),
567             Renderer::Vertex( x1, y2, 0xccFFFFFF, 0.0, 1.0 )
568           );
569           surface->DrawTextNoClip( Scintilla::PRectangle(x1,y1,x2,y2), *mShaderEditor.GetTextFont(), y2 - 5.0, it->first.c_str(), (int)it->first.length(), 0xffFFFFFF, 0x00000000);
570           y1 = y2 + nMargin;
571         }
572       }
573 
574       char szLayout[255];
575       Misc::GetKeymapName(szLayout);
576       std::string sHelp = "F2 - toggle texture preview   F5 or Ctrl-R - recompile shader   F11 - hide GUI   Current keymap: ";
577       sHelp += szLayout;
578       surface->DrawTextNoClip( Scintilla::PRectangle(20,Renderer::nHeight - 20,100,Renderer::nHeight), *mShaderEditor.GetTextFont(), Renderer::nHeight - 5.0, sHelp.c_str(), (int)sHelp.length(), 0x80FFFFFF, 0x00000000);
579     }
580 
581 
582     Renderer::EndTextRendering();
583 
584     Renderer::EndFrame();
585 
586     Capture::CaptureFrame();
587 
588     if (newShader)
589     {
590       // Frame render successful, save shader
591       FILE * f = fopen(Renderer::defaultShaderFilename,"wb");
592       if (f)
593       {
594         fwrite( szShader, strlen(szShader), 1, f );
595         fclose(f);
596         mDebugOutput.SetText( "" );
597       }
598       else
599       {
600         mDebugOutput.SetText( "Unable to save shader! Your work will be lost when you quit!" );
601       }
602     }
603   }
604 
605 
606   delete surface;
607 
608   MIDI::Close();
609   FFT::Close();
610 
611   Renderer::ReleaseTexture( texPreviousFrame );
612   Renderer::ReleaseTexture( texFFT );
613   Renderer::ReleaseTexture( texFFTSmoothed );
614   for (std::map<std::string, Renderer::Texture*>::iterator it = textures.begin(); it != textures.end(); it++)
615   {
616     Renderer::ReleaseTexture( it->second );
617   }
618 
619   Renderer::Close();
620 
621   if ( !sPostExitCmd.empty() )
622   {
623     Misc::ExecuteCommand( sPostExitCmd.c_str(), Renderer::defaultShaderFilename );
624   }
625 
626   FFT::Destroy();
627 
628   Misc::PlatformShutdown();
629 
630   return 0;
631 }
632