Forum: Mikrocontroller und Digitale Elektronik Hochzahlen?


von Tina (Gast)


Lesenswert?

Hallo bin noch Anfänger in Sachen C und hab mal an euch hier eine Frage.
Also ich benötige um Midi Noten Nummern in die jeweilige Frequenz zu 
berechnen Hochzahlen.
(Vieleicht kann das ja hier jemand als Code Schnipsel gebrauchen :-))

Die Berechnung lautet: 440 * 2^((Midinote - 69) / 12)
(zur Berechnung dient dabei a4 440Hz und Midi-Notennummer 69)
und meine Frage bezieht sich jetzt, wie man dies in C umsetzt, denn das
Zeichen ^ funktioniert leider nicht :-/.

schon mal vielen Dank im Vorraus für eure Hilfe

Tina

von Thomas (Gast)


Lesenswert?

Zählschleife, die (Midinote - 69) / 12) durchlaufen wird (geht halt nur 
für ganze Zahlen in der Potenz) und in jedem Durchlauf dem Ergebnis mit 
dem Startwert 440 den Faktor 2 verpasst...

von yalu (Gast)


Lesenswert?

1
#include <math.h>
2
...
3
double frequenz;
4
...
5
frequenz = 440 * pow(2, (Midinote - 69) / 12.0);
Die Berechnung erfolgt hier (bis auf die Subtraktion) in Fließkomma,
deswegen auch 12.0 statt der 12. pow() ist die Potenzfunktion aus der
C-Standardbibliothek.

von Tina (Gast)


Lesenswert?

Prima danke!!!

aber als #define kann man es dem Compiler nicht direkt klar machen oder?

sowas wie ein Makro z.B.
#define setfrequenz (notenum) (440 * 2^(notenum/12))
(natürlich wenn es so richtig geschrieben wäre ;-) )

tina

von Karl H. (kbuchegg)


Lesenswert?

Tina wrote:
> Prima danke!!!
>
> aber als #define kann man es dem Compiler nicht direkt klar machen oder?
>

Warum soll man dafür kein #define machen können.

Ein #define bewirkt nichts anderes als eine Textersetzung
im Quelltext.

Wenn dein Programm so aussieht:


#include <math.h>

int main()
{
  double frequenz;
  frequenz = 440 * pow(2, (Midinote - 69) / 12.0);
}

dann kannst du jeden beli belibigen Abschnitt davon heraus-
ziehen und dafür ein Makro machen:

zb. möchte ich für Midinote - 69 die Bezeichnung MIDI_A
haben. MIDI_A hat logischerweise ein Argument, nämlich
die zu bearbeitende Midinote

also
#define MIDI_A(x)    ( x - 69 )

und als Programm schreibe ich dann:

#include <math.h>

#define MIDI_A(x)  ( x - 69 )
int main()
{
  double frequenz;
  frequenz = 440 * pow(2, MIDI_A(Midinote) / 12.0);
}

Das hats nicht wirklich gebracht :-)
Besser wäre es doch, anstelle der ganzen Formel ein
Makro zu machen. Wenn schon, denn schon
Also: ich möchte gerne den Text

   440 * pow( 2, ( Midinote - 69 ) / 12.0 )

durch ein Makro abdecken, wobei dieses Makro einen
Parameter hat:

#include <math.h>

#define TO_FREQ(x) ( 440 * pow( 2, ( x - 69 ) / 12.0 ) )

int main()
{
  double frequenz;
  frequenz = TO_FREQ(Midinote);
}

Also: machen kann man das schon.
Die sehr viel wichtigere Frage ist aber: Will man denn das
so machen?
Es ist OK, wenn man einfache Ausdrücke oder Konstane per
Makro vom Präprozessor ersetzen lässt. Für komplexere Dinge,
wie diese Berechnung ist es besser eine Funktion zu definieren:

#include <math.h>

double toFreq( unsigned char Note )
{
  return  440 * pow( 2, ( x - 69 ) / 12.0 );
}

int main()
{
  double frequenz;
  frequenz = toFreq( Midinote );
}

Ist auf lange Sicht sehr viel sicherer.

Nur als Beispiel:
Wenn ich da oben zb. definiert hätte

#include <math.h>

#define MIDI_A(x)  x - 69
int main()
{
  double frequenz;
  frequenz = 440 * pow(2, MIDI_A(Midinote) / 12.0);
}

dann hätte ich einen schwer zu findenden Fehler eingebaut.
Um den zu sehen, machen wir mal genau das, was auch der
Präprozessor macht: Eine textuelle Ersetzung des Makros
bei seiner Verwendung durch den Inhalt.

Ersetze also mal in

440 * pow(2, MIDI_A(Midinote) / 12.0);

den Text 'MIDI_A(Midinote)'
durch den Ersetzungstext 'x - 69' wobei für x Midinote eingesetzt
wird. Man erhält

440 * pow(2, Midinote - 69 / 12.0 );

Und jetzt überlege mal genau, wie hier die Punkt vor Strich
Rechnung ein anderes Ergebnis liefert als für

440 * pow(2, ( Midinote - 69 ) / 12.0 );

Durch Betrachten von

#define MIDI_A(x)  x - 69

  frequenz = 440 * pow(2, MIDI_A(Midinote) / 12.0);

ist diese Falle aber nicht unmittelbar zu sehen.

Makros sind Textersetzungen! Sie kümmern sich nicht
um C Regeln!
Für Konstante oder einfache Ausdrücke mag das OK sein.
Für alles andere nimmt man aber besser Funktionen.

von Karl H. (kbuchegg)


Lesenswert?

Übrigens:
Für dein Beispiel nimmst du besser die Variante
mit den Schleifen anstelle der pow Funktion.

Du kannst dir auch schön eine Funktion schreiben
1
//
2
// Berechnet 2 hoch x
3
//
4
int myPow2( int x )
5
{
6
  int i;
7
  int Result = 1;
8
9
  for( i = 0; i < x; ++i )
10
    Result = 2 * Result;
11
12
  return Result;
13
}

  

von yalu (Gast)


Lesenswert?

Wollte gerade auch ne Antwort posten, haber aber gerade noch
rechtzeitig die Superallesumfassendehundertzwanzigprozentantwort von
Karl Heinz gesehen. Der gibt es nun wirklich absolut nichts mehr
hinzuzufügen :D (wie übrigens bei den meisten seiner Antworten)

Huch da ist ja schon der nächste Beitrag von Karl Heinz. Diesem hätte
ich aber doch noch etwas hinzuzufügen:

@Karl Heinz:

Tina will Zweierpotenzen mit gebrochenen Exponenten berechnen, da
bringt deine myPow2-Funktion nicht so arg viel.

von Karl H. (kbuchegg)


Lesenswert?

yalu wrote:
> Tina will Zweierpotenzen mit gebrochenen Exponenten berechnen, da
> bringt deine myPow2-Funktion nicht so arg viel.

Mea culpa.
Ich hatte nur noch die Midinote als Ganzzahl im Hinterkopf.
Die Division durch 12 hab ich glatt verschlafen.

Also: Wie yalu richtig sagt: Mit Schleifen wird das nichts.

von Tina (Gast)


Lesenswert?

Danke nochmal!

Nein geht leider nicht mit ganzahligen Werten :-( und ich frage mich 
jetzt, ob ein Avr mit 16Mhz in einer Zeit von maximal 96µs (z.B. 3* 32µs 
minimale Zeit, zwischen zwei NoteOn Befehlen), das alleine berechnen 
kann, oder aber es nicht doch besser wäre, die Frequenzen in eine 
Tabelle abzulegen. Leider würde mich dies bei 128 Noten dann min. 256 
bytes bei int und 512 bytes bei float kosten .. muss ich mal abwägen :-/

Tina


von Thomas S. (Gast)


Lesenswert?

Ich glaube das eigentliche Rechnen wird auch nicht viel kleiner. Tabelle 
ist für die wenigen Daten sicher die schnellste Lösung.

von Christoph db1uq K. (christoph_kessler)


Angehängte Dateien:

Lesenswert?

So hat man das früher angenähert, ein Top-Octave-Synthesizer für 
Elektronische Orgeln von SGS 1981. Die 12 Frequenzteiler liefern 
angenähert Frequenzen im Abstand der 12.Wurzel von 2

von yalu (Gast)


Lesenswert?

Vorschlag zum Kompromiss:
1
#include <stdint.h>
2
3
double midifreq(uint8_t note) {
4
  static double factors[12] = {
5
    8.17579891564, /* midifreq(0)               */
6
    8.66195721803, /* midifreq(0) * 2 ** (1/12) */
7
    9.17702399742, /* midifreq(0) * 2 ** (2/12) */
8
    9.72271824132,
9
    10.3008611535,
10
    10.9133822323,
11
    11.5623257097,
12
    12.2498573744,
13
    12.9782717994,
14
    13.7500000000,
15
    14.5676175474,
16
    15.4338531643  /* midifreq(0) * 2 ** (11/12) */
17
  };
18
19
  return factors[note % 12] * (1 << (note / 12));
20
}
Diese Lösung ist wesentlich schneller als die pow-Lösung und benötigt 
eine deutlich kleinere Tabelle als die reine Tabellenlösung. Sie ist gut 
für einen Wertebereich für note von 0..255. Wird mehr gebraucht, kann 
der Typ von note uint8_t in uint16_t geändert werden. Dann benötigen die 
beiden Integer-Divisionen etwas länger.

von Christoph db1uq K. (christoph_kessler)


Lesenswert?

Zur Ergänzung, der Hersteller empfiehlt eine Taktfrequenz vn 2,00024 MHz 
oder für die schnelleren Typen ohne -A 4,00048 MHz, damit liegt die 
höchste Frequenz auf der Note "C".

Für diese Methode würde das Progamm also eine Halbton-Tabelle von 12 
16-Bit Zahlen enthalten, zweckmäßig die 16-Bit-Timerwerte für die 
unterste Oktave. Für höhere Oktaven werden die Bits nach rechts 
geschoben.
Die Midinotennummer wird durch 12 geteilt, das ganzzahlige Ergebnis ist 
die Oktave, der Teilerrest der Halbton. Mit den 9 Bit-Zahlen aus dem 
Datenblatt bleiben rechts noch 7 Null-Bits zum Schieben, also 8 Oktaven 
ohne Verlust, ab der 9. Oktave wird die Frequenz ungenauer.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.