MoinMoin,
dann versuche ich mal, wie im letzten Beitrag angekündigt, mein Problem
zu formulieren:
Ich bin gerade dabei, meine Floppy-Musikmaschine zu erweitern, das sie
Musik auf 8 Floppys gleichzeitig machen kann.
Dazu hab ich mir 8Structs geschaffen, die jeweils Tonhöhe des nächsten
Tones, Tondauer des nächsten Tones und bisher gespielte Gesamtdauer
beiinhalten.
Nachdem ein Ton gespielt wurde, und die Systemzeit die Zeit von
Gesamtdauer auf dem entsprechenden Kanal erreicht hat, wird ein Flag
gesetzt.
Dann wird die kürzeste Gesamtdauer herrausgesucht, (das ist der nächste
zu ändernde Ton) im entsrpechenden Struct wird die Dauer des nächsten
Tons rausgesucht, und zur Gesamtdauer hinzuaddiert.
Der Teil klappt soweit auch ganz gut.
Die Sortierfunktion benötigt im Worst-Case (alle Elemente falschrum,
also in aufsteigenden Werten, wenn die Funktion absteigend sortiert)
5155 CPU-Takte was bei 8MHz 644.38µs entspricht.
Da höchstens auf die Millisekunde genau neue Töne kommen, passt das
soweit.
Jetzt kommt aber mein Problem. Ich muss ja nicht nur "umschalten" wenn
der neue Ton kommt, sondern die Tonerzeugung selbst ist ja ein ständiges
Umschalten mit einer Frequenz der Tonhöhe. Sprich wenn ich einen Ton von
1KHz spielen möchte, müsste ich schon jede ms den Lesekopf einen Schritt
ausführen lassen. Bei höheren entsprechend öfter.
Eigl wollte ich die Tonerzeugung genauso wie die Tonauswahl machen. Also
ein Array in dem drin steht, bei welchem Zählerstand der Pin getoggelt
werden muss, welches nach jedem toggeln neu sortiert wird, damit dann
wieder der Ton mit dem niedrigsten Wert am Anfang steht. Nur kann ich
hier ein wesentlich kleineres Zeitfenster haben als eine ms.
Nehmen wir an, ich hab 2 Tone, und mein Timer läuft mit Prescaler
8(sprich ein Timertick = 1µs. Ton1 toggelt immer wenn der Timer 1000
Schritte weiter ist, (also 1KHz) Ton2 ist ein Tick tiefer, sagen wir bei
Timer 1050,(ein über den Daumen gepeilter Halbton tiefer).
Dann müsste ich ja die Liste mit den Umschaltzeiten nach 1000µs neu
sortieren, was im Worst-Case aber 644.38µs entspricht (Es wird ja gleich
sortiert wie oben, und die Elementezahl ist gleich), ich hätte hier aber
nur 50 µs Zeit.
Meine Frage ist nun, ob es da ein besseres Konzept zu Sortierung gibt
oder meinetwegen auch zum Auswählen gibt.
Ich hoffe, meine Problem ist halbewegs verständlich rübergekommen.
MfG Chaos.
Im Anhang gibs auch nochmal den Quelltext, den ich bisher geschrieben
hab. Gerne auch hierzu Kommentare, Verbesserungsvorschlage usw. (Ich
hoffe, dem ein oder anderen fällt auf, das ich langsam versuche, mir
sowas wie strukturiertes Programmieren zu lernen.)
Direkt die nächste Frage:
Mir wurd das alles ein wenig unübersichtlich, und nach ein wenig lesen
hab ich es tatsächlich hinbekommen, eine Funktion in eine extra .c Datei
"auszulagern".
(Meine Sortierfunktion, bei der mir noch ein zwei Kniffe eingefallen
sind, um sie kleiner zu machen. Zumindest an Codezeilen. Denn dank der
Zeigerarithmetik kann ich die Funktion nun endlich so schreiben, das ich
inkrementieren kann, statt jedes Element einzeln mit if abzufragen.=) so
aber nun zurück zum Thema)
In meiner .h Datei habe ich mit:
void Sortieren (uint16_t SortierArray[], uint8_t HerkunftFlag) meine
Funktion deklariert. In dieser tauchen noch weitere Variablen auf,
welche ich aber nicht in der .h deklarieren kann, wenn ich das tue
bekomme ich den Fehler die Variable sei unbekannt. So wie ich das
verstanden hab, soll man in der .h alle Variablen deklarieren, aber
nicht definieren. (Ich meine auf einer Seite stands andersrum, ich hab
beide Varianten probiert). Aber anscheinend klappt das hier nur, wenn
ich lediglich das bekannt mache, was ich der Funktion übergebe... woran
könnte das liegen?
P.S. Könnte ein Mod das bitte ins GCC-Unterforum schieben? ich glaube,
da ists doch wieder besser aufgehoben. Danke =)
P.P.S. In der main frage ich noch einzeln per if ab, wie könnte ich das
per Inkrementierung lösen?
j. t. schrieb:> Die Sortierfunktion benötigt im Worst-Case (alle Elemente falschrum,> also in aufsteigenden Werten, wenn die Funktion absteigend sortiert)> 5155 CPU-Takte was bei 8MHz 644.38µs entspricht.
Ich denke nicht dass es noetig ist, das ganze Array zu sortieren da da
ja nur die kleinste Zeit suchst. Um das Minimum zu suchen must du nur
einmal ueber das Array laufen, und den Index des kleinsten Wertes merken
!
ZigZeg
bisher siehts so aus:
if (SysTick_ms == Melodie0.Gesamtdauer)
{
NeuerTonFlag |= (1 << 0);
}
if (SysTick_ms == Melodie1.Gesamtdauer)
{
NeuerTonFlag |= (1 << 1);
}
Das hier ist mein struct
struct Melodie{
uint16_t Ton[50];
uint16_t Dauer[50];
volatile uint16_t Gesamtdauer;
}Melodie0, Melodie1, Melodie2, Melodie3, Melodie4, Melodie5, Melodie6,
Melodie7;
Wäre das dann
for (i = 0; i < 8; i++)
{
ptr = &(Melodie + ((i+1)*(50 *2)+1) //i+1 damit beim nullten Element
if (*ptr = SysTick_ms) //nicht mal null, 100 mal einmal
für
{ //jedes Element Ton und Dauer,
plus
do blabla //ein für die Gesamtdauer
}
}
könnte das so klappen?
Kai S. schrieb:> j. t. schrieb:>> Die Sortierfunktion benötigt im Worst-Case (alle Elemente falschrum,>> also in aufsteigenden Werten, wenn die Funktion absteigend sortiert)>> 5155 CPU-Takte was bei 8MHz 644.38µs entspricht.>> Ich denke nicht dass es noetig ist, das ganze Array zu sortieren da da> ja nur die kleinste Zeit suchst. Um das Minimum zu suchen must du nur> einmal ueber das Array laufen, und den Index des kleinsten Wertes merken> !>> ZigZeg
Hey das ist mal ein kluger Tip!! Danke dafür, d.h. ich kann direkt nach
dem ersten Durchlauf abbrechen, da ja automatisch dann schon das
kleinste Element da ist!!
Werd ich direkt mal umsetzen
j. t. schrieb:> P.P.S. In der main frage ich noch einzeln per if ab, wie könnte ich das> per Inkrementierung lösen?j. t. schrieb:> könnte das so klappen?
1. Mach aus Melodie ein Array:
j. t. schrieb:> for (i = 0; i < 8; i++)> {> ptr = &(Melodie + ((i+1)*(50 *2)+1) //i+1 damit beim nullten Element> if (*ptr = SysTick_ms) //nicht mal null, 100 mal einmal> für> { //jedes Element Ton und Dauer,> plus> do blabla //ein für die Gesamtdauer> }> }
soll das hier heißen:
1
for(i=0;i<8;i++)
2
{
3
ptr=&(Melodie+((i+1)*(50*2)+1)//i+1 damit beim nullten Element
4
if(*ptr=SysTick_ms)//nicht mal null, 100 mal einmal für
Mit einem Interrupt alle 1ms wirst du nicht weit kommen (für den
"Klangerzeuger").
Damit kannst du max. Frequenzen von 500 Hz abspielen, und die Abstufung
der Tonhöhen ist sehr grob.
Ich mache mal einen Vorschlag:
Du implementierst eine Interrupt Routine, die nur für die Klangerzeugung
zuständig ist.
Diese wird (nach Möglichkeit) so 20000x pro Sekunde aufgerufen.
In der Interrupt Routine hast du 8 Counter, die runterzählen.
Immer wenn ein Counter auf 0 ist, setzt du ihn wieder auf einen Wert,
den du dir von einer globalen Variable holst, und schubst den
entsprechenden Stepper-Motor einen Schritt.
Du brauchst dann noch flags, ob der Kanal überhaupt einen Ton spielen
soll.
Zusätzlich inkrementierst du in der Interrupt-Routine einfach eine
globale 32-bit Variable, das dient dir als "Timecode".
Bei 20KHz reicht das für 1h Musik.
Dein Hauptprogramm macht dann nichts anderes mehr, als die 8 globalen
Variablen und die "Kanal-soll-überhaupt einen ton spielen"-flags
entsprechend zum Timecode zu setzen.
Dazu brauchst du eigentlich in der Firmware auch nichts sortieren.
Lege einfach zwei arrays an. Eines mit einem 32-bit Wert für den
Timecode, und ein 8-dimensionales für die Tonhöhen, die zu diesem
Timecode gehören.
Dein Hauptprogramm wartet jetzt einfach, bis der Timecode, den die
Interrupt Routine erzeugt, dem ersten Timecode in deinem Musik-Array
entspricht (bzw. >= ist).
In dem Moment setzt du die globalen Counter-Werte und gehst in deinen
Arrays einen Schritt weiter... bis die letzte Note erklungen ist.
Den Counterwert für eine Note errechnest du dir, indem du im Internet
eine Frequenztabelle für Noten suchst, f sei die Frequenz der Note, dann
wäre der zu setzende Counter-Wert 20000/2/f
Also zum Beispiel für ein 440Hz A wäre der Counter 22.73, aufgerundet =
23.
@easylife:
Danke für den Hinweis, aber dessen bin ich mir bewusst, das ist nur das
Zeitraster, in dem die Töne geändert werden können. Die Töne selbst werd
ich mit timer1 mit prescaler 8, also 1µs Zeitfenster erzeugen. Siehe
auch mein Projekt mit nur einem Floppy.
Beitrag "Musikalisches Diskettenlaufwerk"
aber nun stehe ich halt vor dem Problem, in das 1ms Zeitfenster auch
noch ne Sortierfunktion reinzuquetschen, die mir den kleinsten Wert
raussucht, wann es auf Timer1 an welchem Port Zeit ist, dem Lesekopf
"Beweg dich!!!" zu sagen...
aber der Tip die Schleife nur einmal durchlaufen zu müssen, bringt mich
schonmal ne Ecke nach vorn
@Easylife
Ich hab erst beim 2ten mal lesen begriffen, was für ein geniales
Konstrukt das ist.
wobei das wären doch pro Tonänderung 4byte für den Timecode+ 8byte für
die Tonhöhen, wobei ich fast glaube, das 8bit Auflösung noch zu gering
ist, ich hab in der ersten Version mit 16bit gearbeitet. Wären also 12
byte pro Tonänderung repsektive 20Byte bei 16bit. Ich hab grad nochmal
gelesen 8-dimensional nicht bit. dann wäre ich bei ca 15k Tonwechseln.
Damit sollte was zu machen sein, knapp 2000 pro Kanal, verteilt auf ne
stunde macht das einen Tonwechsel alle 1,8 Sekunden auf jedem Kanal.
Ist die "Schätzung" soweit Richtig? der Mega32 hat ja 32k ca 2k gehen
fürs programm drauf, macht ca 30k für Tonwechsel.
oder sind das etwa kilobit Speicherplatz? ~panisch nachm Datenblatt wühl
Ich stelle grad fest, structs werden im Ram und nicht im Flash
abgelegt... naja eigentlich ja logisch, ständig am flash rumwerkeln wäre
wohl nicht so gut. (der hat doch "nur" ein paar 1000-10000 writecycles?)
als ich Testhalber mal
1
structTonaenderung
2
{
3
uint32_tTimeCodePosition;
4
uint16_tTonhoehen[7];
5
uint8_tAnAusFlag;
6
7
}Tonaenderung[1000];
probiert hab, war mein ram zu 900irgendwas % voll. D.h. wohl doch eher
nur um die 100 Tonänderungen? =(
j. t. schrieb:> wobei das wären doch pro Tonänderung 4byte für den Timecode+ 8byte für> die Tonhöhen, wobei ich fast glaube, das 8bit Auflösung noch zu gering> ist,
Ja, ich hab kurz nach dem "absenden" drücken auch festgestellt, dass das
8-dimensionale Array doof ist.
Guck dir mal MIDI files an.
Es reicht pro Note aus einen Timecode zu speichern, und die Tonhöhe.
Du kannst als Tonhöhe ja direkt die (Halbton-)Note nehmen, dazwischen
gibts ja nichts.
Dann brauchst du nur noch ein Lookup-Table, das dir zu jeder Note den
entsprechenden Counterwert liefert.
Die 8 Dimensionen des Arrays sind natürlich Mist, da du jede Menge
Informationen über Kanäle speicherst, die sich gar nicht ändern.
Es spricht nichts dagegen, die "Noten-Liste" so zu gestalten, dass der
gleiche Timecode mehrmals hintereinander vorkommt.
Du brauchst also einen Timecode und dazugehörig die Kanal-Nummer, wohin
die Note soll, und die Note an sich. Bei MIDI gibts dann zusätzlich noch
die "Velocity", in deinem Fall ja unerheblich. Eine Tonhöhe 0 auf einem
Kanal kann ja bedeuten: Sound aus.
Wenn sich also 4 Stimmen gleichzeitig ändern, hast du also 4 Einträge
mit gleichem Timecode, jeweis aber mit unterschiedlichen Kanal-Nummern
und Noten.
Dadurch kommst du mit 3 eindimensionalen Arrays für das Musikstück klar:
32-bit Timecode, 8-bit Kanalnummer, 8-bit Note (0=off)
+ Lookuptable für Note->Timerwert
j. t. schrieb:> uint32_t TimeCodePosition;
Du brauchst keine 32bit. Du must ja nur speichern wieviel zeit vergehen
muß bis zur nächsten änderung. Sprich statt bei Minute 32 7 Sekunden,
speicherst du die Information in 50ms.
Da meistens auch nur bestimmte Längen erforderlich sind, kannst du
diese auch in eine Tabelle ablegen und kommt dann evt sogar mit 8 Bit
(oder noch weniger) für die Zeit hin. Das erspart Dir 3 Byte.
Easylife schrieb:> Du brauchst also einen Timecode und dazugehörig die Kanal-Nummer, wohin> die Note soll, und die Note an sich. Bei MIDI gibts dann zusätzlich noch> die "Velocity", in deinem Fall ja unerheblich. Eine Tonhöhe 0 auf einem> Kanal kann ja bedeuten: Sound aus.> Wenn sich also 4 Stimmen gleichzeitig ändern, hast du also 4 Einträge> mit gleichem Timecode, jeweis aber mit unterschiedlichen Kanal-Nummern> und Noten.> Dadurch kommst du mit 3 eindimensionalen Arrays für das Musikstück klar:> 32-bit Timecode, 8-bit Kanalnummer, 8-bit Note (0=off)> + Lookuptable für Note->Timerwert
Ja das seh ich dann aber das gleich Problem an anderer Stelle. Nehmen
wir an ich hab pro Kanal 3 Tonänderungen. jeweils zu Unterschiedlichen
Zeiten. Sind im Falle von 32Bit + 8Dimensioal 8Bit 3 Tonänderungen 8
Kanäle.
96Bit pro Tonwechsel.
Oder ich hab 32bit + 8Bit 3Tonänderungen 8 Kanäle, komme dann aber
aufs Problem, den 32bitWert mehrmals speichern zu müssen, wenn ich auf
mehreren Kanälen eine Tonänderung zur gleichen Zeit habe.
Also xBit pro Tonwechsel je nach Anzahl gleichzeitiger Tonänderung....
Gaestchen schrieb:> Du brauchst keine 32bit. Du must ja nur speichern wieviel zeit vergehen> muß bis zur nächsten änderung. Sprich statt bei Minute 32 7 Sekunden,> speicherst du die Information in 50ms.> Da meistens auch nur bestimmte Längen erforderlich sind, kannst du> diese auch in eine Tabelle ablegen und kommt dann evt sogar mit 8 Bit> (oder noch weniger) für die Zeit hin. Das erspart Dir 3 Byte.
So habich es mit dem einzelnen Floppy gelöst =). Aber das auf 8
aufzuteilen, ist ja doch komplizierter als ich dachte.
So wie ich es zur Zeit versuche, braucht die ISR ncoh 54µs, nicht
unendlich weit von 50.... aber auch 50 wär noch zu lang, wenn sie alle
50 aufgerufen werden soll. Sonst bleibt ja zwischen den ISR keine Zeit
mehr für anderes
Falk Brunner schrieb:>>1. Mach aus Melodie ein Array:>> Der OP ist lernresistent. Nomen est Omen!
Son Quatsch, das ist schon lange umgesetzt. Der OP ist nur resistent
dagegen, Sachen zu benutzen, die er nach Ansicht anderer schon gelernt
haben müsste, sie aber noch nicht begriffen hat. Denn
Sachen die man nicht begriffen zu benutzen... Nun sowas machen eher
weniger helle Leute.
j. t. schrieb:> Oder ich hab 32bit + 8Bit 3Tonänderungen 8 Kanäle,
Wenn du mein Posting verstanden hätteste wären es nur 8+8 Bit. Ich gebe
zu ich war mir nicht ganz sicher ob es verständlich genug formuliert
war.
Darum nochmal der Hinweis darauf.
Generell ist das Thema Datenkompression. Dafür ist es gut zu wissen
welche
Daten man hat die man Komprimieren will.
Wieviel Töne hast du insgesammt die du darstellen kannst(Incl Halbtöne)?
Reichen Dir 32 Töne, dann könntest du in den anderen 3 Bit den Kanal
ablegen.
Wieviel verschiedene Tonlängen hast du? reichen da 16? dann würdest du
auch nur 4 Bit benötigen.
Generell solltest du dir überlegen was günstiger ist, zu speichern wann
die nächste Änderung ist, oder die einzelnen Töne mit deren Länge.
> aufs Problem, den 32bitWert mehrmals speichern zu müssen, wenn ich auf> mehreren Kanälen eine Tonänderung zur gleichen Zeit habe.
Du kannst auch eine Maske davorlegen. <8Bit Zeitcode>, <8Bit Maske>, {
für jedes Bit in der Maske <ein Byte Ton> } Je nachdem wo das Bit
gesetzt wird der Ton dem Kanal zugeordnet.
>> So wie ich es zur Zeit versuche, braucht die ISR ncoh 54µs, nicht> unendlich weit von 50.... aber auch 50 wär noch zu lang, wenn sie alle> 50 aufgerufen werden soll. Sonst bleibt ja zwischen den ISR keine Zeit> mehr für anderes
In der ISR nur die Töne abspielen, die auswertung des stream, wann
welche Töne geändert werden, im Hauptprogramm. Zeig mal deine aktuelle
ISR.
Überlege Dir welche Quarzfrequenz für dich gut ist. Eine auf den ersten
Blick sehr krumme Frequenz kann deutlich besseres Timing liefern. z.B.
könnte ein 7,3728MHz Quarz günstiger sein als ein 8MHz Quarz, da er noch
eine zwei mal den Faktor 3 beinhaltet. Die Töne dann besser getroffen
werden würden. Aber ob das so ist müßte an mal nachrechnen.
@ j. t. (chaoskind)
>> Der OP ist lernresistent. Nomen est Omen!>Son Quatsch, das ist schon lange umgesetzt. Der OP ist nur resistent>dagegen, Sachen zu benutzen, die er nach Ansicht anderer schon gelernt>haben müsste, sie aber noch nicht begriffen hat.
Das Ergebnis ist das Gleiche.
1
// es gibt auch Arrays aus structs
2
3
typedefstruct
4
{
5
uint16_tTon[50];
6
uint16_tDauer[50];
7
volatileuint16_tGesamtdauer;
8
}melodie_t;
9
10
melodie_tmelodie[8];
11
12
for(i=0;i<8;i++){
13
if(SysTick_ms==melodie[i].Gesamtdauer)
14
{
15
NeuerTonFlag|=(1<<i);
16
}
17
}
>Denn> Sachen die man nicht begriffen zu benutzen... Nun sowas machen eher>weniger helle Leute.
Grammatik? Oh, welch Ironie! ;-)
@Gästchen:
Du meinst also, das ich das zuweisen des neuen Tones auch ausserhalb der
ISR machen sollte? Also ein Flag setzen, statt den neuen Ton zu laden?
@Gästchen:
Danke dir, das ist ja ne Menge auf einmal, da muss ich mich jetzt
erstmal durcharbeiten.
Gleich eine kleine Frage dazu:
Was bedeutet "if (! --1)?
Falls nicht einer abgezogen wird??? Es macht gerade sehr laut
"hääääääääää??!!??!!" in meinem Kopf :D
j. t. schrieb:> Gleich eine kleine Frage dazu:> Was bedeutet "if (! --1)?
du meinst if ( ! --cnt ) ... ?
die variable cnt wird eins runtergezält, dann wird sie mit ! (not) auf
== 0 getestet.
Sprich cnt wird eins runtergezählt und wenn sie Null ist, wird die If
Anweisung ausgeführt.
Achso, das war also nur ein Schreibfehler und sollte "if (! ==1)"
heißen?
oder ist das wieder so eine trickreiche Kurzschreibweise a la "i++" für
"i=i+1", und heißt tatsächlich "if (! --1). Dann verstehe ich aber
nicht, worauf da getestet wird? Fehlt da dann nicht noch eine Variable?
j. t. schrieb:> Achso, das war also nur ein Schreibfehler und sollte "if (! ==1)"> heißen?
(! ==1) macht keinen Sinn, auch (! --1) macht keinen, auch (! ==cnt)
würde keinen machen. Ich habe nichts davon geschrieben, worauf beziehst
du Dich?
ohmein gott, ich schlaf zuwenig... da steht cnt und nicht 1.....
jetzt versteh ichs! so halb zumindest.
in welcher Reihenfolge wird da jetzt geprüft? Erst ob das ! zutrifft,
und wenn ja wird cnt ein kleiner gemacht? oder wirds erst ein kleiner
gemacht?
Hast du ja schon gesagt, erst wird dekrementiert, dann geprüft. Gut, ich
glaub, ich habs doch
j. t. schrieb:> in welcher Reihenfolge wird da jetzt geprüft? Erst ob das ! zutrifft,> und wenn ja wird cnt ein kleiner gemacht? oder wirds erst ein kleiner> gemacht?
zuerst komt --cnz, das ist equivalent zu (cnt-=1) oder (cnt=cnt-1), also
wird zuerst cnt um eins verkleinert. Danach wird das logisch nicht
darauf angewendet, equivalent zu x==0
bei x-- wird zuerst x zurückgegeben und irgendwann innerhalb der
anweisung dekrementiert, deshalb ist x=x-- undefiniert
Bastler schrieb:>>> uint8_t TonhoehenZaehler[8]; // nicht innerhalb sonst gehen die Daten
verloren
> Aber nur wenn main() verlassen wird. Dann ist aber alles verloren!
Seit wann bleiben Variablen die innerhalb Funktionen angelegt werden
solange erhalten bis die main() verlassen wird? Dir ist schon klar das
der Kommentar sich auf seinen Code bezieht? Wo er das Feld innerhalb
einer Funktion angelegt hat aber immer wieder darauf zugegriffen hat.
Wenn die Funktion, um die es geht, () heißt z.B.
Aber ich sehe gerade, in der ISR deklariert er die ja noch mal lokal.
Wie war noch mal der Titel: "Wege aus dem Chaos", oder so ;-)
Heureka!
Es funktioniert soweit, ich kann unabhängig unterschiedliche Töne
ausgeben. Nun muss ich nur noch austüfteln, welche Werte zu welchen
Tönen gehören.
Vielen Dank für die Denkanstöße an euch, ich werd den Fortschritt hier
weiter dokumentieren, zur Zeit siehts so aus: siehe Anhang.
Hat doch schon viel Schönes.
Trotzdem würde ich dir empfehlen, das 8-dimensionale Array wieder
aufzugeben, um Speicher zu sparen.
Die Counterwerte zu den Tonhöhen legst du am besten in einem lookup
table ab.
Dann kannst du in deiner Melodiestruct für die Tonhöhe einen Notenwert
ablegen. Da würde ich mich an MIDI orientieren, später kannst du dir
dann einen Converter schreiben, der dir direkt MIDI Files in dein Struct
umwandelt.
1
structMelodien
2
{
3
uint8_toffset;
4
uint8_tkanal;
5
uint8_tnote;
6
};
7
8
conststructMelodienMelodie1[]=
9
{
10
{16,0,200},// 16
11
{0,1,200},
12
{0,2,200},
13
{0,3,200},
14
{0,4,200},
15
{0,5,200},
16
{0,6,200},
17
{0,7,200},
18
19
{16,0,0},// 32
20
{0,1,0},
21
{0,2,0},
22
{0,3,0},
23
{0,4,0},
24
{0,5,0},
25
{0,6,0},
26
{0,7,0},
27
28
{12,0,12},// 44
29
30
{4,1,16},// 48
31
32
{8,0,18},// 56
33
{0,1,14},
34
35
{8,1,16},// 64
36
37
{8,0,12},// 72
38
{0,1,14},
39
40
{8,1,16},// 80
41
42
{8,0,14},// 88
43
44
{8,0,12},// 96
45
{0,1,13},
46
47
{4,0,16},// 100
48
{0,1,14},
49
50
{2,1,12},// 102
51
52
{2,1,18},// 104
53
54
{2,1,12},// 106
55
56
{1,1,18},// 107
57
58
{1,1,12},// 108
59
60
{2,1,18},// 110
61
62
{8,0,0},// 118
63
{0,1,0},
64
65
{255,0,0}// offset=255: Ende vom Lied
66
};
67
68
69
constuint16_t[]freq_lookup=
70
{
71
0,
72
1,
73
2,
74
3,
75
4,
76
(...)
77
};
78
79
80
(...)
81
82
intmain(void)
83
{
84
85
(...)
86
87
uint8_toffset;
88
uint16_tn=0;
89
uint16_tposition=0;
90
91
do()
92
{
93
offset=Melodie1[n].offset;
94
95
position+=offset;
96
97
while(SysTick_64tel<position)// warten bis nächste Position erreicht
Haha du bist genial!
Ich mache mir grad Gedanken darüber, was ich anders machen könnte, um
auf ein sinnvolleres Timing zu kommen. Nach genau sowas hab ich
gesucht!! =).
Dann hab ich eine weitere Timingfrage, aber dazu mach ich n neues Thema
auf.
Irgendwas mit den BPM haut nicht hin, und ich komm nicht drauf was...
Und wie ich die Frage schreibe, und mir das Problem nochmal vor Augen
führe, kommt mir die Lösung!
Das Problem war, das ich meine BPM noch mal 4 nehmen musste, und ich mir
die 4 nicht erklären konnte. Bis ich drauf kam, das ein einzelner Beat
ja 4 64tel Noten lang ist, und nicht eine!
Korrektur: BPM sind nicht Takte pro Minute, sondern das 3 oder 4 fache
davon (je nachdem, ob das Musikstück im 3/4 oder 4/4 Takt ist).
BPM bezieht sich auf also 1/4 Noten.
Daher passen 16 1/64-tel Noten zwischen zwei Beats.
Als erstes:
const uint16_t[] freq_lookup == const uint16_t freq_lookup[] ????
falls nicht, wo liegt der Unterschied?
und dann:
nehmen wir an n ist gerade 0. Dann würde er hier doch
Kanal.Tonhoehe[Melodie1[n].kanal] = freq_lookup[Melodie1[n].note];
"nachgucken" freq_lookup[Element an der Stelle, was an der Adresse von
Melodie1[n].note steht] an Melodie1[0].note ist 200. Also steht da bei n
= 0:
freq_lookup[200] ???
wäre da nicht sinnvoller:
#define NOTE1 0
#define NOTE2 1
.
.
const struct Melodien Melodie1[] =
{
{ 16, 0, NOTE1},
.
const uint16_t[] freq_lookup =
{
200,
210,
.
.
.
}
????
Kanal.Tonhoehe[Melodie1[n].kanal] = freq_lookup[Melodie1[n].note];
und ganz klar ist mir auch nicht, was denn nun genau auf den Wert
gesetzt.
Bei mir ist es ja bis jetzt:
Kanal.Tonhoehe[i] und i wird inkrementiert... 0-7
und in Melodie1[n].kanal steht ja auch immer maximal 0-7.. ah ok den
Teil hab ich glaub ich begriffen.
Gedacht ist es so, dass du in deinem Melodie-Array Notenwerte ablegst,
keine krummen Counterwerte.
Für die Notenwerte würde ich mich am MIDI-Standard orientieren:
http://www.berkhan.com/basic/manual_7/manual_d/data_d/chap7-3.htm
Um jetzt die Counter deines Klangerzeugers entsprechend zur Note zu
setzen, gibt es das freq_lookup table.
Note 0 sollte eine Ausnahme bleiben, und auch in der freq_lookup table
eine 0 haben (damit der Klangerzeuger weiss, dass kein Ton gespielt
werden soll).
Aber schon bei Note 1 kannst du anfangen, einen sinnvollen Counterwert
in die Tabelle einzufügen.
Meine Tabelle (1, 2, 3, 4, 5) ist nur exemplarisch.
Nehmen wir an, dein Klangerzeugungsinterrupt wird 10000x pro Sekunde
aufgerufen.
MIDI Note 1 ist C#1 = 34.8 Hz
(http://www.thundermix.de/files/frequenztabelle.gif)
Counterwert für C#1 = 10000 2 34.8 = 144 (gerundet).
MIDI Note 2 ist D1 = 37.1 Hz -> Counterwert 10000 2 37.1 = 135
und so machst du gerade weiter, bis du bei MIDI Note 86 bist (D8)
D8 = 4752 Hz -> Counterwert = 10000 2 4752 = 1
Deine lookuptable sähe bei 10000Hz Interrupt-Frequenz also in etwa so
aus:
const uint16_t[86] freq_lookup =
{
0,
144,
135,
(...)
1
}
Mit "const" deklariert man in der Regel Arrays, die nur gelesen werden,
also nie ein Wert verändert werden soll.
Der Compiler meckert dann, wenn man versuchen sollte dies zu tun. Nur
diesen Sinn hat es.
Die Zeile
Kanal.Tonhoehe[Melodie1[n].kanal] = freq_lookup[Melodie1[n].note];
ist in der Tat nicht ganz einfach zu verstehen.
Auf der linken Seite passiert folgendes:
Aus deinem Melodie-Array wird der Wert des Kanals rausgesucht, der sich
ändern soll.
Nimm z.B. die erste Zeile deiner Melodie
{ 16, 0, 200 },
Sobald also Position 16 erreicht ist, ist Melodie[0].kanal = 0
Jetzt wird also Kanal.Tonhoehe[0] ein neuer Wert zugewiesen.
Den Wert holt sich das Programm aus freq_lookup.
Da Melodie1[0].note 200 ist, würde es jetzt freq_lookup[200] nehmen.
Die Note 200 macht tatsächlich in deinem Fall aber keinen Sinn, da du ja
bei Note 86 schon bei 5000 Hz ankommst.
Aber als Beispiel taugt es ja.
Im nächsten Schritt deine Melodie steht dann
{ 0, 1, 200 },
Da der zeitliche Offset hier 0 ist, wird dieser Schritt direkt nach dem
ersten ausgeführt, also quasi passiert sie gleichzeitig.
Jetzt ist der Kanal 1, d.h. diese Note wird dem Kanal 1 zugewiesen.
Und so weiter.
Etwas schwierig per Text zu erklären. Guck dir den Quellcode nochmal
genau an, was der macht.
In der do-while Schleife wird nur dann gewartet, wenn sich "position"
ändert. Solange die offsets in deinem Musikstück 0 sind, läuft die
do-while Schleife sehr schnell durch, und ändert die Tonhöhe mehrerer
Kanäle quasi gleichzeitig.
Und wenn du das Ganze dann mal am Laufen hast, dann hätte ich noch eine
Erweiterungsidee für dich:
Das Protokoll, dass MIDI Keyboards sprechen ist sehr einfach zu parsen.
Du brauchst im Wesentlichen einen RS232 Eingang mit passender Baudrate.
Da fallen dann direkt Note-ON und Note-OFF Messages raus, mit besagter
MIDI-Noten-Nummer.
Damit könntest du deinen Klangerzeuger direkt live mit einem
MIDI-Keyboard spielen ;-)
Wunderbar, dank dir nochmal für deine Erläuterungen! =)
Und die live-Spielbarkeit ist genau das "Langzeitziel"
Kleine Ergänzung:
Counterwert für C#1 = 10000 2 34.8 = 144 (gerundet).
Die durch 2 ist hier glaub ich unnötig, da ich hier ja kein Rechteck
ausgebe, bei dem ich für eine "Schwingung" einmal hin und zurückschalten
muss, sondern jeder einzelne Step entspricht schon einer Schwingung.
Irgendwas funktioniert nicht so richtig an deinem Vorschlag....
Irgendwie spielt er mir nur noch die ersten beiden Töne ab....
Wenn ich das debugge, dann fängt er mit erreichen von 16 bei
Systick_64tel an, die nächsten Werte zu laden, aber braucht er da pro
Wert 1/64 Note. Sprich obwohl ich beim offset null bin, ist der 7te Ton
von zweiten Block nicht innerhalb einer 64tel geladen, sondern erst bei
23/64tel.
Ich vermute aber, das liegt nur daran, das die Timer weiterlaufen, auch
wenn ich gerade kein Schritt mache... Also hier nochmal die Frage, wie
kann ich die Timer beim debuggen "ausstellen", das der Prescaler also
immer nur ein CPU-Takt sieht, wenn ich einen Step mache. bzw so viele
CPU-Takte, wie der Befehl braucht.
P.S.
Du hast recht, solche Probleme/Lösungen schreibend zu beschreiben, ist
garnicht so einfach. Während man den einen Teil schreibt, fällt einem
zum anderen noch was ein, bis man den einen Teil fertig hat, ist das vom
anderen aber schon wieder vergessen.
Hat sich geklärt, ich hatte nur keine Notenwerte geladen... Die
Betriebsblindheit.
Wobei die Frage nach dem "run timer in stopped mode" immer noch
ungeklärt ist.
Ich hab angefangen einen Kommentar zu schreiben, ein wenig meine
Gedanken zu sortieren und aufzuschreiben, dabei ist mir etwas
aufgefallen. Der Kommentar ist dann mehr oder weniger ein Fragebeitrag
geworden, also kopier ich ihn mal hierein.
Programm das 8 Töne erzeugen und über die Leseköpfe von 8 3,5"
Floppylaufwerken ausgeben kann. Die Ansteuerung der Laufwerke
erfolgt über die Pins 12(floppy on/off), 18(direction) und 24(step). Pin
12 wird auf Masse gelegt um die Floppys zu aktivieren.
(evtl später noch per Schieberegister anzusteuern, müssen aktiviert
sein, damit der Kopf Schritte machen kann, schalten
also langsam --> Schiebereg sollte also reichen.)
Die direction-Pins werden an PORTD angeschloßen, die step-Pins an PORTB.
Direction-Pin auf LOW bedeutet Vorwärts und umgekehrt.
Ein Schritt vom Lesekopf wird mit einer LOW-->HIGH Flanke ausgelöst.
Um einen Ton zu erzeugen muss der Lesekopf x mal einen Schritt machen,
um auf x Hz zu kommen. Zeitbasis für die Tonerzeugung ist 100µs, d.h.
der höchste "sinnvolle" Ton dürfte bei rund 1kHz liegen, da der Lesekopf
bei 1kHz alle 10 Zeitbasen einen Schritt macht. 1Schritt mehr oder
weniger entspricht schon einer Tonhöhenänderung von 10% was bereits
knapp 2 Halbtönen entspricht (ein Halbton entspricht etwa 5,9% ( x *
2^(1/12)). Konzept zur Tonerzeugung ändern? Zeitbasis ändern? Wie lang
ist die ISR?
Mein USB-oszi lässt sich lustigerweise nicht mehr installieren, ich hab
aber noch ein Multimeter das Frequenz und Pulsweite messen kann. Sollte
ich damit nicht auch die Länge der ISR bestimmen können? Pin am Anfang
high setzen, am Ende auf low, dann müsste ich doch an der Frequenz sehen
können ob meine 100µs stimmen (entspricht 10kHz), und wenn die Pulsweite
sagen wir mal 5% beträgt, hätte ich doch noch genügend Luft um die
Zeitbasis auf 10µs zu verkürzen. Die Länge der ISR ändert sich nicht,
das hieße dann eine Frequenz von 100kHz bei 50% Pulsweite und somit 50%
Rechenzeit. Die anderen 50 sollten doch für die Hauptschleife reichen?
Ah bei der kann ich die Länge ja Sogar im Simulator ermitteln...
10.01kHz und 13% Pulsweite. Mh fünteln fiele mir da als erstes ein.
das wären dann 50kHz bei 65% Pulsweite. Ist eine ISR-Zeit von 65% noch
vertretbar? Da müsste ich wohl schauen, wie lange der "Idle-State" ist.
Schön von wenn funktioniert was man sich ausdenkt =)
P.S.
Der ist fast durchgehend an.
PORTA |= 0b00000010;
while (SysTick_64tel < position);
PORTA &= 0b11111110;
Nur zu den Tonwechseln zuckt die Frequenzanzeige mal kurz, die Spannung
ist wird durchgehend als 4.96V angezeigt. Das heißt doch, das ich von
den 87% der Zeit, die ich nicht in der ISR bin, bin ich fast durchgehend
am nichts tun, also sollte die Zeitbasis doch verzehnfachbar sein?
bei ner verzehnfachung hat er statt den erwarteten 100kHz nur 69
ausgeben, bei 70% pulsweite, ich bin nun doch bei mal 5, da komm ich auf
50.07kHz und 50% Pulsweite.
Langsam kann ich mich daran machen, die OCR-Werte für die Töne zu
berechnen.
Kannst du oder wer anders, mir evtl noch ein paar Takte zu ISR-Dauern
erzählen? Ich weiß, sie sollen kurz sein, keine unnötigen Berechnungen
usw.
Aber ich mein allgemeinere Sachen, so in Richtung Rechenzeitverwaltung.