Wahl der "richtigen" Programmiersprache für ein Audio-Projekt



  • Und hier dasselbe (hoffentlich) in C++ zusammengefrickelt (VS 2010):

    #include "stdafx.h"
    #include <math.h>
    #include <iostream>
    using namespace std;
    #include "windows.h"
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	const int sampleRate = 44100;
    	const double leaky = 0.99;
    	double position = 0;
    	double delta = 1;
    	double max = 0;
    	double posSinc = 0;
    	double dcOffset = 0;
    	double out = 0;
    
    	// Impulse-Train Sweep Test
    	LONGLONG g_Frequency, g_FirstNullCount, g_LastNullCount, g_FirstCount, g_LastCount;
    
    	if (!QueryPerformanceFrequency((LARGE_INTEGER*)&g_Frequency))
    		printf("Performance Counter nicht vorhanden");
    
    	double resolution = 1000000 / ((double)g_Frequency);
    
    	printf("Frequenz des Counters:  %lld kHz\n", g_Frequency/1000);  //lld -> LONGLONG darstellung
    	printf("Dadurch maximale Aufloesung: %4.5f us\n", resolution);
    
    	//null-messung
    	QueryPerformanceCounter((LARGE_INTEGER*)&g_FirstNullCount);
    	QueryPerformanceCounter((LARGE_INTEGER*)&g_LastNullCount);
    	double nulltime = (((double)(g_LastNullCount-g_FirstNullCount))/((double)g_Frequency)); 
    
    	printf("Null-Zeit: %4.5f us\n", nulltime * 1000000);
    
    	//beginn messung
    	QueryPerformanceCounter((LARGE_INTEGER*)&g_FirstCount);
    
    	for (int frequency = 50; frequency <= sampleRate * 0.5; frequency++)
    	{
    		max = 0.5 * sampleRate / frequency;
    		dcOffset = -0.49 / max;
    		position += delta;
    		if (position < 0)
    		{
    			position = -position;
    			delta = -delta;
    		}
    		else if ((position > max))
    		{
    			position = max + max - position;
    			delta = -delta;
    		}
    		posSinc = 3.14159265358979323846 * position;
    		if (posSinc < 1E-05)
    		{
    			posSinc = 1E-05;
    		}
    		out = leaky * out + dcOffset + sin(posSinc) / posSinc;
    	}
    
    	//2. Messung
    	QueryPerformanceCounter((LARGE_INTEGER*)&g_LastCount);
    
    	double dTimeDiff = (((double)(g_LastCount-g_FirstCount))/((double)g_Frequency)); 
    
    	//Von der gemessenen Zeit die "Null-Zeit" abziehen, um genauer zu werden
    	double time = (dTimeDiff - nulltime) * 1000000; //mikro-sekunden
    
    	printf("Zeit: %4.5fus\n" ,time);
    
    	printf("Dummy-Out: %4.5f\n", out);
    	int get;
    	cin >> get;
    	return 0;
    }
    


  • Bei diesem Vergleich sieht es auf meinem Laptop schon anders aus.

    Die C# Version braucht durchschnittlich 1412 Mikrosekunden.
    Die Ergebnisse der C++ Version schwanken sehr stark zwischen 950 und 1420 Mikrosekunden (offenbar ist die Messmethode sehr ungenau).



  • Brauchst du wirklich einen Performancecounter? Normalerweise sollte die Messung schon so ~2 Sekunden dauern, ansonsten schwankt das zu stark. Kannst du das so umbauen, dass es länger dauert?
    Edit: Wenn du "sampleRate * 0.5" durch "sampleRate * 500" ersetzt, dauert es ca. 2 Sekunden. Damit kannst du dann auch vernünftig messen. Nur ist natürlich klar, dass hier eigentlich nur die Geschwindigkeit der sin() Funktion gemessen wird. 😉

    #include <cmath>
    #include <ctime>
    #include <iostream>
    
    int main()
    {
      const int sampleRate = 44100;
      const double leaky = 0.99;
      double position = 0;
      double delta = 1;
      double max = 0;
      double posSinc = 0;
      double dcOffset = 0;
      double out = 0;
    
      auto t = std::clock();
    
      for (int frequency = 50; frequency <= sampleRate * 0.5; frequency++)
      {
          max = 0.5 * sampleRate / frequency;
          dcOffset = -0.49 / max;
          position += delta;
          if (position < 0)
          {
              position = -position;
              delta = -delta;
          }
          else if ((position > max))
          {
              position = max + max - position;
              delta = -delta;
          }
          posSinc = 3.14159265358979323846 * position;
          if (posSinc < 1E-05)
          {
              posSinc = 1E-05;
          }
          out = leaky * out + dcOffset + sin(posSinc) / posSinc;
      }
    
      auto used_time = (std::clock() - t) / static_cast<double>(CLOCKS_PER_SEC);
      std::cout << "Time: " << used_time << "\tOut: " << out;
    
      std::cin.get();
    }
    


  • Naja, keine Ahnung wegen dem Performance-Counter, ich hab ja von C++ Null Ahnung 🙂

    Zum testen braucht die Funktion ja keinen wirklichen Sinn ergeben, daher hab ich jetzt mal die Range von 1 Hz bis 5000 * Sample-Rate erweitert und die Sinus-Funktion rausgeworfen.

    Hier der C# Code:

    using System;
    using System.Diagnostics;
    
    namespace ConsoleApplication2
    {
        class Program
        {
            static void Main(string[] args)
            {
                const int sampleRate = 44100;
                const double leaky = 0.99;
                double position = 0;
                double delta = 1;
                double max = 0;
                double posSinc = 0;
                double dcOffset = 0;
                double @out = 0;
                Stopwatch sw = new Stopwatch();
    
                sw.Start();
                for (int frequency = 1; frequency <= Convert.ToInt32(sampleRate * 5000); frequency++)
                {
                    max = 0.5 * sampleRate / frequency;
                    dcOffset = -0.49 / max;
                    position += delta;
                    if (position < 0)
                    {
                        position = -position;
                        delta = -delta;
                    }
                    else if ((position > max))
                    {
                        position = max + max - position;
                        delta = -delta;
                    }
                    posSinc = Math.PI * position;
                    if (posSinc < 1E-05)
                    {
                        posSinc = 1E-05;
                    }
                    @out = leaky * @out + dcOffset / posSinc;
                }
                sw.Stop();
                Console.WriteLine(string.Format("Dummy-Out: {0}", @out));
                Console.WriteLine(string.Format("{0}µs", sw.ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L))));
                Console.ReadKey();
            }
        }
    }
    

    Und hier Dein angepasster Code (womit kriege ich das kompiliert? MinGW meckert das er ctime nicht kennt):

    #include <cmath> 
    #include <ctime> 
    #include <iostream> 
    
    int main() 
    { 
      const int sampleRate = 44100; 
      const double leaky = 0.99; 
      double position = 0; 
      double delta = 1; 
      double max = 0; 
      double posSinc = 0; 
      double dcOffset = 0; 
      double out = 0; 
    
      auto t = std::clock(); 
    
      for (int frequency = 1; frequency <= sampleRate * 5000; frequency++) 
      { 
          max = 0.5 * sampleRate / frequency; 
          dcOffset = -0.49 / max; 
          position += delta; 
          if (position < 0) 
          { 
              position = -position; 
              delta = -delta; 
          } 
          else if ((position > max)) 
          { 
              position = max + max - position; 
              delta = -delta; 
          } 
          posSinc = 3.14159265358979323846 * position; 
          if (posSinc < 1E-05) 
          { 
              posSinc = 1E-05; 
          } 
          out = leaky * out + dcOffset / posSinc; 
      } 
    
      auto used_time = (std::clock() - t) / static_cast<double>(CLOCKS_PER_SEC); 
      std::cout << "Time: " << used_time << "\tOut: " << out; 
    
      std::cin.get(); 
    }
    


  • Ich empfehle dir C++ für das Audio Processing und C# für die GUI.



  • dot schrieb:

    Ich empfehle dir C++ für das Audio Processing und C# für die GUI.

    Ja, wäre wohl die Beste Lösung, dann käme ich sicherlich besser voran 🙂



  • GoaZwerg schrieb:

    (womit kriege ich das kompiliert? MinGW meckert das er ctime nicht kennt):

    Hm..? Komischer Fehler. Probier mal mit:

    g++ -std=c++0x -O3 -o freq.exe freq.cpp
    

    Ich kompiliere sonst mit Visual Studio. Das Programm braucht bei mir mit VS ~9 und mit GCC ~10 Sekunden. Die teuerstens Zeilen sind:

    dcOffset = -0.49 / max; // 16.6%
    // ..
    out = leaky * out + dcOffset / posSinc; // 76.3%
    

    Wahrscheinlich kann man da aber nicht mehr so viel optimieren.. bist du denn jetzt mit dem Ergebnis zufrieden? (Insofern du das denn kompilieren kannst, MinGW mit GCC 4.6.1 läuft aber problemlos durch.)

    Edit:

    dot schrieb:

    Ich empfehle dir C++ für das Audio Processing und C# für die GUI.

    Und ich empfehle dir für beides C#, da du das 1. besser zu können scheinst, du 2. kein Gefummel mit zwei Sprachen hast und C# 3. mit Sicherheit schnell genug ist. 😉
    (Nur wenn das Audiozeug plattformunabhängig sein soll, dann nimm vielleicht doch lieber C++.)



  • Die Performance ist der Grund weshalb ich ihm für den Teil zu C++ raten würde. C# ist sicher ausreichend für einfache Dinge und man kann mit unsafe Blöcken und so schon einiges rausholen. Aber wenn er wirklich möglichst effizient komplexeres Streamprocessing betreiben will, dann wird er SSE verwenden wollen, und da geht's eben in Bereiche wo man mit C# keine Chance hat.
    Wenns also was einfaches sein soll, reicht C# vermutlich aus. Aber bei komplexeren Operationen wird man früher oder später C++ aufsuchen, wenn man Performance will.
    Wenn es also was Komplexeres ist und performant sein soll, dann würd ich für den performanceintensiven Teil gleich auf C++ setzen, anstatt früher oder später den C# Code nach C++ zu portieren. Eine einfache dll + C-API könnte ausreichen und das ist in C# sehr einfach einzubinden.
    Ansonsten natürlich einfach alles in C# machen.



  • Ja, optimiert werden müsste dieser Code eh nicht, dass war nur zum testen, in echt würden die Sinc-Funktionen ja vorberechnet werden.

    Mit Visual Studio hat das kompilieren geklappt, danke 🙂

    Die C# Version ist bei mir etwas mehr als 1 Sekunde langsamer, bei unterschiedlichen Funktionen könnte sich das sicherlich auch wieder relativieren.

    Aber ich werd das jetzt so machen das ich die GUI in C# und die kritischen Sachen in einer C++ DLL auslagere.

    Danke euch für alle Tipps 👍



  • Meiner Erfahrung nach kann ich dir nur sehr dazu raten, das ganze Audio-Processing wirklich schön gekapselt in einer dll zu haben und um keinen Preis irgendwie mit dem C# Teil zu vermischen, auch wenn sich das anfangs vielleicht anbietet. Die C# GUI sollte wirklich nur auf deiner dll die Tasten drücken, ansonsten wirst du dir früher oder später wünschen, dass du es so gemacht hättest 😉

    Ich hab hier zumindest ein Projekt, wo ich mir heute wünschen würde, dass ich es so gemacht hätte 😉
    (Zum Glück muss ichs nichtmehr warten, denn das anstehende Refactoring käme einem Rewrite gleich)


Anmelden zum Antworten