relativ einfaches metronom



  • Guten Tag nochmal,

    hab hier nochmal eine überarbeitete version von einem metronom, ist das so in Ordnung, oder würdet ihr da was verbessern?:

    #include "stdafx.h" 
    #include <iostream> 
    #include "windows.h" 
    #include <fstream>
    
    using namespace std; 
    
    static const char seq[]="sequence.txt";  //die textdatei wo die sequenz drin ist
    
    class metronom
    { 
    private: 
    
        LARGE_INTEGER frequency;       //CPU clock
        LARGE_INTEGER t1, t2;          //t1 entspricht Startzeit,t2 der laufenden zeit      
        double elapsedTime;            //( t2-t1)/frequenz
        ifstream eingabe;
        double temp,bpm,hold,oldbpm;   //BPM = SOLLwert  oldbpm=ISTwert, hold= halten
        int seqline;                   //Anzahl der Zeilen in sequence.txt
    
    public: 
    
         metronom();                     
         void tick(void); 
         void readsequence(void);      //füllt die variablen bpm und hold mit leben
    
    }; 
    
    int _tmain(int argc, _TCHAR* argv[]) 
    {       
         metronom m; 
         m.tick(); 
         return 0; 
    } 
    
    void metronom::tick()
    { 
        int i=1,wait=0;                //i ist der Zähler, der bei jedem Tick erhöht wird
        readsequence();
        oldbpm=bpm;
        QueryPerformanceCounter(&t1); //t1 wird EINMALIG mit leben gefüllt
        QueryPerformanceFrequency(&frequency); 
        while(true)
        { 
    	do{ 
            QueryPerformanceCounter(&t2); //t2 wird permanent aktualisiert
            elapsedTime = (t2.QuadPart - t1.QuadPart)*1000000 /frequency.QuadPart; 
    
    //(60000000/oldbpm) ist das delay in microsekunden, die das programm warten muss, 0.997 ist ein selbstermittleter korrekturfaktor, i ist der zähler, der das Delay nach //jedem tick vervielfacht
    
              }while(elapsedTime<((60000000/oldbpm))*0.997*i); 
    
            Beep(440,100); 
            i++;                                                                         //zähler wird erhöht, damit später wieder gewartet werden kann
            wait++;                                                                      // dient dazu, eine BPM-rate eine weile lang zu halten
            if(oldbpm<bpm)      {oldbpm+=1;QueryPerformanceCounter(&t1);i=1;wait=0;}    //ist-wert demm soll-wert mit einer linearen rampe angleichen
            else if(oldbpm>bpm) {oldbpm-=1;QueryPerformanceCounter(&t1);i=1;wait=0;}
            else if(wait>=oldbpm*hold/60){wait=0;readsequence();}// wenn die BPM lange genug gehalten wurde, nächste zeile lesen, neue bpm und hold werte benutzen                                                                       
    
        }
    
    }; 
    
    metronom::metronom(){seqline=0;}; 
    
    // das folgende dient nur dazu, aus einer textdatei, wie ich sie am unteren beispiel aufgeführt hab, BPM-werte, und deren Dauer rauszuholen. 
    void metronom::readsequence()
    {
        eingabe(seq,ios_base::in);
        if(eingabe.good())
        {
    	 eingabe.seekg(0L,ios::beg);
    	 char zeile[10];
    	 int parsed=0;
    	 int c=0,i,j,split;
    	 for(int l=0;l<=seqline;l++) eingabe.getline(zeile,10);
    
    	 if(eingabe.good())
             {
    		 while(zeile[c]!='\0'){c++;}
    		 if(c!=-1)
                     { 
    		         for(i=c-1;i>0;i--)
                             {
    			    if(zeile[i]!=':')
                                {
    			        int pow=1;for(j=1;j<c-i;j++)pow=pow*10;
                                    parsed=parsed+((int)zeile[i]-48)*pow;
    			    }
    			    else
                                {   hold=parsed;parsed=0;i--;split=c-i;
    		                for(i;i>=0;i--)
                                    {
    			           int pow=1;for(j=split;j<c-i;j++)pow=pow*10;
    		                   parsed=parsed+((int)zeile[i]-48)*pow;	 
                                       bpm=parsed;
    
    		                }
    		             }
    		         }
                       }
    
    	   seqline++;
    	   eingabe.close();
             }       
               else{cout<<"EOF"<<"\n";}
       }
    	   else{cout<<"Read failed";}
    
    };
    

    Aufgabe ist, es eine Sequenz von BPM-werten, und ihre Dauer einzulesen, und das dann halbwegs organisch wiederzugeben. Je präziser das ganze, umso besser. Die Sequenz liegt als Textdatei vor, und sieht z.B. so aus:
    40:30
    50:30
    60:30
    70:30
    80:30
    90:30
    100:30
    90:30
    80:30
    70:30
    60:30
    50:30

    links vom doppelpunkt = bpm, rechts = dauer.

    Ich hab das metronom mal mit einem onlinemetronom verglichen, nach etwa 4 minuten hatte meins eine Abweichung von einem Schlag, bei 200 bpm, also ein Schlag zu wenig. Leider kann man sich ja nicht sicher sein, ob das online metronom 100% zuverlässig ist:
    http://www.metronomeonline.com/

    die übergänge sind nicht ganz so organisch. wenn z.b. der Taktschlag von 40 auf 80 erhöht wird in 1-er schritten, dann ist es zwar zwischendurch gleichmäßig steigend, aber nach dem letzten schritt, also wenn 79 auf 80 übergeht, und dann die 80 nochmal kommt, an dieser stelle hört man eine ungleichmäßigkeit, es hört sich so an als ob es schlagartig etwas schneller wird.

    "while(elapsedTime<((60000000/oldbpm))*0.997*i); "

    die 0.997 ist zu Fuß ausgerechnet. Habe das ganze wiegesagt mit dem onlinemetronom verglichen, und dann geschau, wieviel prozent das delay kleiner sein muss, damit 1 Schlag mehr reinkommt. nach 460 Schlägen vom Online Metronom hatte meins nur 459.

    Was haltet ihr bisher davon?



  • ach und mir fällt grade auf dass die "correction" glaub ich bei dieser metronomvariante ziemlich unnötig ist, die gleicht nur ein offset aus, was durch die vergleichsoperation im while-ausdruck erzeugt wird, aber verändert nicht den takt. ich editiert das mal schnell..



  • Ich habs nur überflogen, weil dein Programmierstil - wenn man das überhaupt so nennen kann - ist einfach absolut grauenvoll.

    Die Einrückung ist mal vorhanden und mal nicht. Mal ist sie so breit und mal so. Mal ist die Klammer in der gleichen Zeile und mal in der darauffolgenden, usw. So macht das keinen Spaß sich das anzugucken.



  • Entschuldigung, ich bin noch Anfänger, aber ich versuch das gleich in Ordnung zu bringen.



  • Heilige Scheiße. Ich muss llllllllllll leider rechtgeben - und rate dir, dir schnellstens ein Grundlagenbuch anzuschaffen. So wie das jetzt aussieht...

    Ich bin zwar ein Multithreading-Noob, aber so stelle ich mir das vor:

    #include <thread>
    #include <memory>
    #include <mutex>
    #include <iostream>
    
    #include "windows.h"
    
    struct Metronom
    {
    	using duration_type = std::chrono::milliseconds;
    
    private:
    	std::intmax_t mBPM;
    
    	mutable std::mutex mBPMMutex;
    
    	std::unique_ptr<std::thread> mThread;
    
    	void _tickLoop()
    	{
    		for(;;)
    		{
    			if( !mBPMMutex.try_lock() ) // kk in Erinnerung: "... resourcenfressende Endlosschleife"
    				continue;           // Hier könnte man noch ein this_thread::sleep_for einfügen
    
    			if( !mBPM )
    				break;
    
    			duration_type dur{ duration_type::period::den / mBPM * 60 };
    
    			mBPMMutex.unlock();
    
    			Beep(800, 100);
    
    			std::this_thread::sleep_for( dur );
    		}
    	}
    
    public:
    
    	Metronom():
    		mBPM{} {}
    
    	~Metronom()
    	{
    		if( mThread )
    		{
    			bpm(0);
    			mThread->join();
    		}
    	}
    
    	void bpm( std::intmax_t f )
    	{
    		mBPMMutex.lock();
    		mBPM = f;
    		mBPMMutex.unlock();
    
    		if( !mThread )
    			mThread.reset( new std::thread{ [this]{ _tickLoop(); } } );
    	}
    
    	std::intmax_t bpm() const
    	{
    		std::lock_guard<std::mutex> _{mBPMMutex};
    		return mBPM;
    	}
    };
    
    #include <iostream>
    
    int main()
    {
    	Metronom m;
    
    	std::intmax_t period;
    	while( std::cin >> period && period )
    		m.bpm( period );
    }
    


  • Fehlt da nicht der Ausgleich, wenn sleep nicht exakt korrekt lange schläft? Das Metronom soll ja nicht irgendwann Abweichungen hervorbringen bzw. die wenn wieder ausgleichen.

    Ich würde mir einfach die Anfangszeit merken und immer von dieser Basis aus mit Vielfachen die nächsten Zeitpunkte bestimmen. Dann arbeitet man entweder mit Sleep (angepasst auf Differenz von aktuellem Zeitpunkt zu nächstem auf Anfangszeit basierenden Zeitpunkt) oder hält ne Endlosschleife, die bei größtmöglicher Nähe zum Zeitpunkt den Beep auslöst (ein Sleep(10 /* ms*/); um nicht unnötig Prozessauslastung zu erzeugen reicht da auch völlig).



  • Fehlt da nicht der Ausgleich, wenn sleep nicht exakt korrekt lange schläft? Das Metronom soll ja nicht irgendwann Abweichungen hervorbringen.

    Hui, das habe ich nicht berücksichtigt.



  • Ich würde mir einfach die Anfangszeit merken und immer von dieser Basis aus mit Vielfachen die nächsten Zeitpunkte bestimmen.

    Genau so hab ich das bei mir umgesetzt falls ihr es nicht gemerkt habt, entschuldigt nochmal meine Strukturierung, aber ich wüsste nicht wie das besser gehen soll.

    EDIT:
    Es ist auch irgendwie leicht unfair, denn im Editor ist der Text anders strukturiert als nachher in der Vorschau, das macht es nicht einfach.

    EDIT: Ich füg mal Kommentare ein, dann gleicht das die Unstrukturiertheit etwas aus. und warum " Heilige Scheisse"? Ist es wirklich so schlimm?

    Was tun

    EDIT: Ich merke gerade, mein Programm ist hochgradig uneffizient, CPU-Auslastung 50%, für ein 100KB .exe, das ist ja schlimmer als ein Fourieranalysen-benchmark



  • So, habe mir den Code Mal angeschaut. Sieht doch ganz okay aus, vom Stil abgesehen.

    Also für sauberen Codestil kannst Du sicherlich was googlen. Ich würde nicht mehrere Statements zwischen {} ohne Zeilenumbruch machen und der {}-Block sollte bündig zum Befehl darüber sein. Oder man macht eben so was wie do{ , aber bitte nicht mischen, wie Du es tust. Mehr als eine Leerzeile ist Käse und nach public: oder private: macht man auch keine Leerzeile üblicherweise. Außerdem bitte gleichmäßig einrücken, Du hast manchmal 4 Leerzeichen und manchmal eins, das ist Käse.

    Zu der CPU-Auslastung: Einfach ein Sleep(10); oder so in die innere Schleife stecken.



  • Womit programmierst du denn? Wenn ich z.B. Visual Studio zum Programmieren nutze, dann habe ich keine Probleme mit Einrückungen - auch nicht, wenn ich den Code kopiere.

    Der Code sieht schon etwas lesbarer aus, aber ist noch immer nicht schön.

    Mal ein Beispiel:

    while(true)
        {
        do{
            QueryPerformanceCounter(&t2); //t2 wird permanent aktualisiert
            elapsedTime = (t2.QuadPart - t1.QuadPart)*1000000 /frequency.QuadPart;
    
    //(60000000/oldbpm) ist das delay in microsekunden, die das programm warten muss, 0.997 ist ein selbstermittleter korrekturfaktor, i ist der zähler, der das Delay nach //jedem tick vervielfacht
    
              }while(elapsedTime<((60000000/oldbpm))*0.997*i);
    
            Beep(440,100);
            i++;                                                                         //zähler wird erhöht, damit später wieder gewartet werden kann
            wait++;                                                                      // dient dazu, eine BPM-rate eine weile lang zu halten
            if(oldbpm<bpm)      {oldbpm+=1;QueryPerformanceCounter(&t1);i=1;wait=0;}    //ist-wert demm soll-wert mit einer linearen rampe angleichen
            else if(oldbpm>bpm) {oldbpm-=1;QueryPerformanceCounter(&t1);i=1;wait=0;}
            else if(wait>=oldbpm*hold/60){wait=0;readsequence();}// wenn die BPM lange genug gehalten wurde, nächste zeile lesen, neue bpm und hold werte benutzen                                                                      
    
        }
    

    Besser:

    while (true)
    {
    	do
    	{
    		QueryPerformanceCounter(&t2); //t2 wird permanent aktualisiert
    
    		elapsedTime = (t2.QuadPart - t1.QuadPart) * 1000000 / frequency.QuadPart;
    
    		/*	
    			(60000000/oldbpm) ist das delay in microsekunden, 
    			die das programm warten muss, 0.997 ist ein selbstermittleter korrekturfaktor, 
    			i ist der zähler, der das Delay nach //jedem tick vervielfacht
            */
    	} while (elapsedTime < ((60000000 / oldbpm)) * 0.997 * i);
    
    	Beep(440, 100);
    
    	i++; //zähler wird erhöht, damit später wieder gewartet werden kann
    	wait++; // dient dazu, eine BPM-rate eine weile lang zu halten
    
    	if (oldbpm < bpm) //ist-wert demm soll-wert mit einer linearen rampe angleichen
    	{
    		oldbpm += 1;
    		QueryPerformanceCounter(&t1);
    		i = 1;
    		wait = 0;
    	}    
    	else if (oldbpm > bpm) 
    	{
    		oldbpm -= 1;
    		QueryPerformanceCounter(&t1);
    		i = 1;
    		wait = 0;
    	}
    	else if (wait >= oldbpm * hold / 60) // wenn die BPM lange genug gehalten wurde, nächste zeile lesen, neue bpm und hold werte benutzen              
    	{
    		wait = 0;
    		readsequence();
    	}
    }
    

    Natürlich ist einiges davon Geschmackssache, aber es sollte dir ein Anhaltspunkt geben, wie Code lesbarer wird.



  • Mal eine Frage:

    Welche Bücher und vorallem welche Programmiersprache würdet ihr mir ans Herz legen, wenn es in Richtung hardwarenahe Programmierung und Echtzeitaudioanalyse geht? Assembler soll da sehr effizient sein, da es keine unnötigen Kapriolen springt, aber eine Ganze Software nur in Assembler?
    Ich hab noch nie in Assembler programmiert, muss sich toll anfühlen, wenn man weiß, wie das Bit beim Namen heißt, und was genau im Hintergrund passiert.
    Ich habe schon einen Assemblercode gefunden, wo Fourieranalysen implementiert wurde, allerdings will ich NOCH nicht genauer reinschauen, sonst verzettel ich mich hier wieder.
    Grob gesagt: Mein Ziel ist es ein Metronom auf Hardware umzusetzen, das auf bestimmtes Spielverhalten mit Taktänderungen reagiert.
    Hohes Ziel, hab schon ein sehr gutes kostenloses Buch gefunden zum Thema Digital Signal Processing, allerdings Ebook: www.dspguide.com
    Bin dabei es durchzuarbeiten, mit Aufgaben lösen zur Vertiefung.

    Ihr wisst bestimmt, in welche Richtung ich gehen muss, um mir unnötige Mühe und Ärger zu ersparen?



  • amrosik schrieb:

    Mal eine Frage:

    Welche Bücher und vorallem welche Programmiersprache würdet ihr mir ans Herz legen, wenn es in Richtung hardwarenahe Programmierung und Echtzeitaudioanalyse geht? Assembler soll da sehr effizient sein, da es keine unnötigen Kapriolen springt, aber eine Ganze Software nur in Assembler?

    In Assembler würde ich nur empfehlen wenn du sicher bist, dass das nötig ist. Das ist sehr unkomfortabel und solange du nicht sehr gut darin bist, wird ein Compiler meist besseren Mascheienencode erzeugen. Für welchen Prozessor soll das denn überhaupt sein?

    Prinzipiell ist es durchaus empfehlenswert sich mal etwas mit Assembler zu beschäftigen, weil man dann viel besser versteht was der Compiler überhaupt macht, wie der Stack funktioniert, wie Funktionsaufrufe funktionieren usw. Wirklich in Assembler Programmieren würde ich heute nur noch in einzelnen Subroutinen wenn es um die Ausnutzung von Spezialbefehlen auf spezieller Hardware geht.



  • Ups da habe ich versehentlich den Beitrag zweimal abgeschickt. Kann man Beiträge irgendwie löschen?



  • Nein, aber ersetze den Text in deinem letzten einfach durch ein paar Leerzeichen. Das versteht schon jeder.



  • amrosik schrieb:

    Mal eine Frage:

    Welche Bücher und vorallem welche Programmiersprache würdet ihr mir ans Herz legen, wenn es in Richtung hardwarenahe Programmierung und Echtzeitaudioanalyse geht? Assembler soll da sehr effizient sein, da es keine unnötigen Kapriolen springt, aber eine Ganze Software nur in Assembler?

    Benutz C++ wnen es dir gefällt. Der Compiler wird aller Wahrscheinlichkeit besser als du wissen, wie effizienter assembler aussieht.



  • Der Compiler wird aller Wahrscheinlichkeit besser als du wissen, wie effizienter assembler aussieht.

    Bis dahin wird sich das aller Wahrscheinlichkeit ändern.

    guckt euch mal das Tier an:
    http://elm-chan.org/works/akilcd/report_e.html
    Hier wurde eine FFT direkt in Assembler auf einem ATmega8 umgesetzt.



  • amrosik schrieb:

    Bis dahin wird sich das aller Wahrscheinlichkeit ändern.

    Nein. Compiler werden eher besser als schlechter. Zwischen Assembler können und effizient Assembler anwenden liegen ein paar Jahre Erfahrung (und jede Menge tabellen auswnedig lernen die der COmpiler natürlich alle kennt).



  • was sind diese Tabellen genau? Fallunterscheidungen?



  • Nein. Compiler werden eher besser als schlechter.

    Anfänger werden üblicherweise anders motiviert als so.


Anmelden zum Antworten