Hi zusammen Nach längerer Zeit wage ich die erste Veröffentlichung meines Synthesizer (Anfang: Beitrag "AVR Synthesizer Konzept" ). Der Synth hat folgende Eigenschaften: - 17 frei konfigurierbare Kanäle mit: - Hüllkurven - Volume - (pulsierende Volume (geplant)) - (Vibrato (geplant)) - Sehr schnell und effient < 1Mhz / Kanal - läuft ab 2,9Mhz - 44,1kHz Samplingrate - Bisher 7 Intrumente (beliebig erweiterbar) - 16bit Berechnung - 6-9bit PWM Sound Die Intrumente sind nicht so gut gelungen, es ist aber auch schwer aus einem 256 Wavetable verschiedene Intrumente zu erzeugen. Der Synthesizer läuft mit allen Atmegas, mit der Einschränkung, dass man beim Atmega48 die Kanäle reduzieren muss (SRAM reicht nicht). Bei 17 Kanälen braucht der Synth ~670Byte Ram und bei 7 Instrumenten 3196Byte. Der Synth nutzt ein eigenes Dateiformat um Stücke abzuspielen, genaueres steht im Code. Ich habe auch einen Converter programmiert, welcher die Midi-Dateien konvertiert. Jedoch mischt dieser die Stimmen und beachtet auch keine anderen Midibefehle außer Noten. Schlagzeug kann er auch nicht, allerdings konvertiert der Konverter dieses als Tiefen kurzen Ton, was sich z.T ähnlich anhört. Zur Hardware gibt es nicht viel zu sagen: Man nimmt einen Mega und hängt an seinen 16bit Timer PWM Ausgang einen Lautsprecher dran. Evtl. noch ein Tiefpass davor. Anbei zwei Aufnahmen, des Synthesizers bei 7bit PWM @8Mhz @44100Hz mit 4 Kanälen. Euch nun viel Spaß damit! Grüße, Samuel
Hier noch der Converter. Die Exe befindet sich unter SequencerDemo\Bin\Debug. Ich muss noch dazusagen: Der Konverter funktioniert manchmal nicht so richtig. Das liegt wahrscheinlich am Timing. Einen richtiger Midikonverter liest eben die Mididatei und konvertiert sie dabei und spielt sie nicht ab während er die Midisignale abfängt.
Hier nochmal 2 Beispiele, welche die Leistung des Synthesizers demonstrieren (er läuft immer noch mit seinen schiefen 8Mhz (man müsste ihn mal stimmen): - Cantine Band (Star Wars): 9 Kanäle - Mario: 8 Kanäle Die Stücke werden leider nicht vollständig gespielt, da Flash voll ist. Bei beiden wurde der Atmega8 mit ~8000Byte programmiert. Wahrscheinlich läuft es letzendlich auf eine SD-Karte hinaus. Solltet ihr Fragen, bzw. (Verbesserungs-)Wünsche haben, stellt sie ruhig.
Sehr schick! Wie sieht denn die Beschaltung des AVR aus? Lautsprecher via C an PWM-Kanal oder hast du nur LineOut erzeugt (direkt an PC)?
Die Beschaltung des Avrs hab ich angehängt (aus Klangerzeugung). Zum Aufnehmen schließe ich keinen Lautsprecher an, sondern gehe mit dem Kabel zum LineIn.
>Hier nochmal 2 Beispiele, welche die Leistung des Synthesizers >demonstrieren Klingt gar nicht schlecht. Ein großes Lob für Deine Arbeit von mir. Ich könnte mir Vorstellen, dass es reichen würde, die Wavetable-Sounds mit nur 4 Bit zu digitalisieren, da Du ja beim Mischen sowieso Dynamik verlierst. Ich habe vor einiger Zeit experimentiert und 4 Bit klangen auch noch gut. Eventuell könnte man auch sowas wie my-law oder a-law Kompression machen. Dann dürfte allerdings die Kanalzahl etwas weniger werden. Gruß, chris
chris schrieb: > Ich könnte mir Vorstellen, dass es reichen würde, die Wavetable-Sounds > mit nur 4 Bit zu digitalisieren, da Du ja beim Mischen sowieso Dynamik > verlierst. Das stimmt, Dynamik geht verloren. Ein 4bit Table auszulesen kostet halt 2 Takte mehr. Die ganze Routine ist nur so schnell, weil eigentlich nur berechnet und gemischt (10 Takte) und das für die nächsten 64 Samples. Variablen werden vorher geladen. Der Hüllkurvenwert wird vorher mit der Lautstärke multipliziert. Bei der Tonerzeugung gibt temp2 das Intrument an. temp2 wird in ZH geschrieben und der richtige Wavetable wird ausgelesen. Deswegen wäre Kompression eher fatal.
1 | ;Frequenzähler |
2 | add Ztemp, hrfreq ;zum Zähler addieren |
3 | adc temp, hrfreq2 |
4 | |
5 | ;Tonerzeugung (Sample auslesen) |
6 | movw Z, temp:temp2 |
7 | lpm presc, Z |
8 | |
9 | ;Lautstärke |
10 | mulsu presc, volsum |
11 | |
12 | ;Zum Rest mischen |
13 | add sample, r0 |
14 | adc sample2, r1 |
Ich werde versuchen den Synthesizer + SD-Karte in einen Atmega8 zu bekommen. Elm-Chans PetitFat Dateisystem scheint das kleinste im Netz zu sein, damit werde ich es probieren.
Ein paar Bugfixes: Der 8bit pwm Modus lief nicht, komischerweise kann man das Lowbyte des OCR1A Registers nicht ohne das Highreg schreiben. Ich finde den Klang im 8bit Modus deutlich besser. Außerdem funktionierte die Tonfrequenzrechnung nicht richtig (Töne waren von F_CPU abhängig). Anbei noch den Entertainer im Drehorgelsound. Zurzeit versuche ich den Synth um eine SD-Karte zu erweitern, dafür muss ich den Asmcode wahrscheinlich nach C portieren, da es kaum gute asm-sd-libs gibt.
Wie Stehts mit dem Projeckt? Welche Instrumente kann er schon Immitieren? Wäre auch (E)Gitarre möglich? oder Hammond-Orgel? Sind zwei verschiedene Instrumente gleichzeitig Möglich?
Projekt läuft noch. Die Portierung nach C ist inzwischen abgeschlossen, er kann auch Dateien von der SD-Karte abspielen. Diese müssen von midi erst konvertiert werden (neuer Konverter ist auch schon programmiert). Da der Grundaufbau nun fertig ist, gehts es leider jetzt erst an neue Instrumente (derzeit ist nur ein Testinstrument implementiert). Wenn man die SD-Karte nutzt, passt es leider nicht mehr in einen Mega8: Die SD-Karten-Ansteuerung braucht 700B, dazu kommen noch 256B Lesepuffer. Bastler schrieb: > Wäre auch (E)Gitarre möglich? > oder Hammond-Orgel? Es sind alle Instrumente möglich - die andere Frage ist wie gut sie nachgeahmt werden können. Das neue System nutzt (wie ElmChans Synth auch) je 256 Samples für Attack (Anschwingen des Tons) und für Sustain (Nachklang). > Sind zwei verschiedene Instrumente gleichzeitig Möglich? Ja, jeder Kanal kann ein eigenes Instrument spielen.
Hallo, Ich suche eine Funktion in c um eine Hüllkurve zu berechnen. kannst Du bitte diesen codeschnippsel Posten?
Was für eine Hüllkurve? Ich berechne nur das Abklingen durch eine Parabel: Am Anfang wird eine 8bit Variable auf 255 gesetzt. Diese wird im Timer-Interrupt um einen Wert, der die Abklinggeschwindigkeit bestimmt, erniedrigt. Zum Berechnen der Hüllkurve wird das Sample mit dem Quadrat des Wertes multipliziert.
wow, ziemlich cooles Projekt! :) Bei Gelegenheit werde ich es austesten. Gruß Stephan
Es gibt nun einen (deutlich) komfortableren Midikonverter in dem Synthesizerartikel. Dadurch ist es möglich gleich mehrere Dateien in einem Rutsch zu konvertieren. Außerdem bestimmt der neue Konverter das Tempo der Mididateien selbst und die Instrumente müssen nicht mehr manuell zugeordnet werden. Konvertiert werden die meisten Mididateien, allerdings gibt es auch manche wenige Mididateien die noch fehlerhaft konvertiert werden. In der Synthesizerlib wurde einer kritischer Bug behoben, der die Funktion des Synths dem Compiler überließ. Das heißt wenn man Glück hatte, funktioniert er. Aber anscheinend hat es keiner bemerkt...
Sam .. schrieb: > Es gibt nun einen (deutlich) komfortableren Midikonverter in dem > > Synthesizerartikel. wo? Eine Anmerkung zu der PWM: Hinter eine PWM gehört ein richtiger Integrator, nicht nur ein Dämpfungsglied, sonst gibt es Verzerrungen.
Wirklich ein super Projekt! Ich habe mir mal aus deinem Artikel das Flash Beispiel gezogen. Folgende Dinge sind mir aufgefallen: 1. Am Anfang dieses Thread schreibst du läuft mit allen ATmegas, der Code schränkt dieses aber deutlich ein. 2. Dein Makefile beinhaltet ein F_CPU=2500000UL. Das ist nur eine beliebige Konstante. Trotzdem - wenn ich das richtig sehe - ist keiner der verwendeten Mikrokontroller für 25MHz ausgelegt. 3. In Synthesizer.h wird synth_config.h included. Das gibt es aber nicht. Richtig muss es Synth_config.h heißen (großes S). 4. Du solltest dokumentieren, dass du eine externe Clock-Source verwendest (lfuse Settings!). Ich habe das zur Probe mal eben - wirlich nebenbei - auf einen ATmega32 gepumpt mit lfuse 0xe4 (8 MHz). Zumindest geht das schon mal. Anbei die dafür für mich notwendigen Änderungen an deinen Sourcen. Eventuell sind die Änderungen am Makfile nur mit GNUmake verträglich :-) Das Projekt hat das Potential Chips wie den SAE800 sinnlos erscheinen zu lassen!
Thomas Müller schrieb: > 2. Dein Makefile beinhaltet ein F_CPU=2500000UL. Das ist nur eine > beliebige Konstante. Trotzdem - wenn ich das richtig sehe - ist keiner > der verwendeten Mikrokontroller für 25MHz ausgelegt. Wieso 25MHz ? Ist ja 2,5 MHz das können wohl alle. Oben steht ab 2,9MHz gehts
Typo meinerseits. Hier das orginal c&p CFLAGS += -Wall -gdwarf-2 -std=gnu99 -DF_CPU=25000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums => das entspricht ca. 25Mhz. btw. sind auch 2,5 MHz nur mit externem Quartz zu realisieren und das lohnt sich bei der Anwendung nicht. Da kann man dann die internen 4 oder 8MHz hernehmen. Ist ja keine Uhr :-)
Thomas Müller schrieb: > 1. Am Anfang dieses Thread schreibst du läuft mit allen ATmegas, der > Code schränkt dieses aber deutlich ein. Er läuft auf allen, außer auf dem Atmega48 (zu wenig Ram und Flash). Der Synthesizer benötigt mindestens 7KB Flash und 500-900 Byte Ram. Bei einem Atmega8 bleiben somit nur 1 KB für die Song-Daten übrig. Daher ist dieser nur theoretisch nutzbar. Man könnte die Daten aber auch in einem externem EEPROM/Flash speichern. > 2. Dein Makefile beinhaltet ein F_CPU=2500000UL. Das ist nur eine > beliebige Konstante. Trotzdem - wenn ich das richtig sehe - ist keiner > der verwendeten Mikrokontroller für 25MHz ausgelegt. Es gibt tatsächlich keinen MegaAvr, der für 25 Mhz ausgelegt ist. Die meisten (besonders die Neueren) lassen sich ohne Probleme ein paar Mhz übertakten. Ich habe hier den Atmega328 auf 25 Mhz übertaktet um ein Stück mit >30 Kanälen abzuspielen. Das Makefile habe ich außerdem nicht selbst geschrieben, sondern wurde von AvrStudio generiert. > 3. In Synthesizer.h wird synth_config.h included. Das gibt es aber > nicht. Richtig muss es Synth_config.h heißen (großes S). Danke, werde ich korrigieren. > 4. Du solltest dokumentieren, dass du eine externe Clock-Source > verwendest (lfuse Settings!). Werde ich machen. Ein externer Quarz ist zwingend notwendig, wenn die Töne die richtige Höhe haben sollen. Mikel M. schrieb: > Oben steht ab 2,9MHz > gehts Es geht übrigens auch mit kleineren Fequenzen, wenn man in der Synth_config.h die Samplingrate reduziert: 2,9 Mhz / 44100 Hz = 66 Takte --> 6bit PWM 6bit PWM ist noch erträglich, darunter wird grausam.
vielleicht ist im zusammenhang dieses interessant Beitrag "[AVR] FM-Transmitter für 1,50EUR --> Ansteuerung?" ist nen atmega32L, der mmr70 günstig zu bekommen <1Euro. man müsste den ausgang nur mit dem eingang des fm moduls koppel, dann könnte man es direkt streamen, oder fm modul ignorieren und kleinen buzzer dran.
Sam .. schrieb: > Thomas Müller schrieb: >> 1. Am Anfang dieses Thread schreibst du läuft mit allen ATmegas, der >> Code schränkt dieses aber deutlich ein. > > Er läuft auf allen, außer auf dem Atmega48 (zu wenig Ram und Flash). Wir reden von zwei unterschiedlichen Betrachtungsweisen :-) Mein Patch bezog sich auf die Möglichkeit den Code auch direkt für Atmega 8/16/32 übersetzen zu können (habe nichts anderes parat). Dein Code setzt das Vorhandensein eines TIMSK1 Registers voraus. Dieses gibt es aber bei den o.g. Typen nicht => Fehler beim compilieren. Für den PIN OC1A läuft der Code schlicht in einen Pre-Compiler Fehler. Beides ist in meinem Patch für die genannten Typen gefixt. > Es gibt tatsächlich keinen MegaAvr, der für 25 Mhz ausgelegt ist. Die > meisten (besonders die Neueren) lassen sich ohne Probleme ein paar Mhz > übertakten. Ich habe hier den Atmega328 auf 25 Mhz übertaktet um ein > Stück mit >30 Kanälen abzuspielen. Das mag sein. Ich versuche mich an die Spezifikation zu halten. Für das was ich vorhabe sollten 10 Kanäle locker reichen. > Das Makefile habe ich außerdem nicht selbst geschrieben, sondern wurde > von AvrStudio generiert. Kann ich ja nichts für, wenn AvrStudio solche Makefiles schreibt. Dennoch nehme ich an, dass du den Wert für F_CPU gesetzt hast :-) Ich hab mir inzwischen mein eigenes Makefile geschrieben, dass direkt im Quellcode-Verzeichnis liegt und das Programmieren des AVR auch noch mit kann. Nun nochmal zum eigentlichen Anliegen. Den Synth finde ich wirklich super und ich würde gerne meine Haustürklingel, die zur Zeit mit einem SAE800 implementiert ist (sch* Sound) durch eine Lösung mit deinem Synth ersetzen. Trozt wirklich intensiver Studie deines Code komme ich nicht richtig weiter. Was ich möcht ist: - Der AVR soll in den Sleep Modus. - Bei Auslösung eines ext. INT soll er seine Musi abspielen und bei Ende dann wieder in den Sleep Modus. - Wenn während des Abspielens erneut ein ext. INT ausgelöst wird, soll das Abspielen vom Anfang neu beginnen. Ich habe bisher vergeblich in deinem Code nach dem deterministischen Ende gesucht. Also was passiert, wenn wenn aller Sound abgespielt ist? Mein Versuchsaufbau mit einem Mega32 läßt leider vermuten, dass der Code dann irgendwie ins Nirvanna läuft. Kannst du mit einen Tipp geben?
Zuerst würde ich dir das SD-Karten-Beispiel empfehlen. Da wird u.a. auch geprüft, ob der Synthesizer noch läuft. Thomas Müller schrieb: > Trozt wirklich intensiver Studie deines Code komme ich nicht richtig > weiter. Was ich möcht ist: > - Der AVR soll in den Sleep Modus. Grundsätzlich kannst du in der Hauptschleife dauernd in den Sleep Modus gehen. Du hast bspw. eine Hauptschleife:
1 | for(;;) |
2 | {
|
3 | synth_update(); |
4 | sleep_mode(); |
5 | }
|
Wichtig ist hier, dass synth_update() regelmäßig aufgerufen wird (1 kHz Aufrufrate). Der Synthesizer löst 44100 mal in der Sekunde einen Interrupt aus und weckt den µC, dadurch hast du hier nichts zu befürchten. Nur muss der Timer-Interrupt den Sleep Mode unterbrechen können. > - Bei Auslösung eines ext. INT soll er seine Musi abspielen und bei Ende > dann wieder in den Sleep Modus. Am besten setzt du in deinem INT ein Job-Flag, welches den Synthesizer startet. Gestartet wird er, indem man die Sound-Datei lädt:
1 | int8_t seq_load(ReadFunc read, uint8_t* data); |
Das muss man, wenn der synth von vorne spielen soll, jedes mal machen. Thomas Müller schrieb: > Also was passiert, wenn wenn aller Sound abgespielt ist? Der Sequenzer schaltet sich ab und fütter den Synthesizer nicht mehr mit Noten. Das findet in der synth_update() Funktion statt:
1 | uint8_t active = 0; //Diese Variable prüft ob alle Oscs beendet sind |
2 | do
|
3 | {
|
4 | if(oscs->state < OSC_STATE_END) |
5 | {
|
6 | active = 1; //Das wird nur ausgeführt wenn der Kanal noch aktiv ist. |
7 | [...]
|
8 | }
|
9 | oscs++; |
10 | }
|
11 | while(--counter); |
12 | [...]
|
13 | if(!active) |
14 | seq_stop(); |
Den aktuelle Zustand kann man mit dem Makro seq_running() überprüfen. > - Wenn während des Abspielens erneut ein ext. INT ausgelöst wird, soll > das Abspielen vom Anfang neu beginnen. Den Sequenzer einfach mit der abzuspielenden Datei neu füttern. Noch etwas zum Sleep Modus: Wenn du den Interrupt des Synthesizers abschalten willst, kannst du ihn über die synth_stop()-Funktion anhalten. Im Powerdown Sleep Mode kannst du das aber auch weglassen, da dort Timer den µC nicht aufwecken können.
Sam .. schrieb: > Zuerst würde ich dir das SD-Karten-Beispiel empfehlen. Ich habe die notwendige HW nicht und ich brauche sie auch nicht - für dieses Vorhaben. > Wichtig ist hier, dass synth_update() regelmäßig aufgerufen wird (1 kHz > Aufrufrate). synth_update() ? Meintest du eventuelle seq_update(), welches als MSEC_CFUNC1 in Synth_config.h definiert wird? Sonst verstehe ich es schon wieder nicht :-( > Der Sequenzer schaltet sich ab und fütter den Synthesizer nicht mehr mit > Noten. Das findet in der synth_update() Funktion statt: Ich hab's immer nocht nicht ... synth_update() rufst du in deinem main() permanent auf. Dann gibt es noch den Timer Interrupt und seq_update(), welches offensichtlich aus synth_update() als MSEC_CFUNC1 via mycall aufgerufen wird. Aus dem Code ist mir noch nicht ersichtlich woran das Ende der Sounddaten erkannt wird. uint8_t ReadFlash(uint8_t* pos) gibt einfach nur das nächste Byte ohne irgend einen Status. Ist der Status in dem gelesenem Byte versteckt (EOF Markierung)? Ich glaub ich muss deinen Code noch etwas studieren... Vielen Dank für deine Ausführungen!
Thomas Müller schrieb: >> Wichtig ist hier, dass synth_update() regelmäßig aufgerufen wird (1 kHz >> Aufrufrate). > synth_update() ? > Meintest du eventuelle seq_update(), welches als MSEC_CFUNC1 in > Synth_config.h definiert wird? Sonst verstehe ich es schon wieder nicht > :-( Die synth_update aktualisiert die Synthesizerkanäle, berechnet die Samples und ruft auch die seq-update auf. Die Funktion wird über ein internes Job-Flag gesteuert, welches jede Millisekunde gesetzt wird. Daher muss es von dir rechtzeitig ausgerufen werden, damit keine Job-Flag verloren gehen. Wenn man sie zu langsam aufruft, dürfte das hörbare Effekte geben. Thomas Müller schrieb: >> Der Sequenzer schaltet sich ab und fütter den Synthesizer nicht mehr mit >> Noten. Das findet in der synth_update() Funktion statt: Da hab ich Mist geschrieben. Hier meinte ich die seq_update(). Daraus ist auch der Codeausschnitt. Thomas Müller schrieb: > synth_update() rufst du in deinem main() permanent auf. > Dann gibt es noch den Timer Interrupt und seq_update(), welches > offensichtlich aus synth_update() als MSEC_CFUNC1 via mycall > aufgerufen wird. Das ist richtig. > Aus dem Code ist mir noch nicht ersichtlich woran das Ende der > Sounddaten > erkannt wird. Das EOF in der Sounddatei ist 0x80. Das muss für jeden Kanal einmal vorkommen. Jeder Kanal wird, sobald er nicht mehr benötigt wird, durch diesen Befehl beendet. Sobald ein Kanal beendet ist, werden für ihn keine neuen Befehle gelesen. Wenn alle Kanäle beendet sind, stoppt sich den Sequenzer selbst. Der letzte Befehl in jeder Sounddatei muss 0x80 sein, damit der letzte Kanal auch beendet wird. Sonst tritt deine Vermutung ein und der Synthesizer spielt weiter ins Nirwana.
Sam .. schrieb: > Thomas Müller schrieb: >> Aus dem Code ist mir noch nicht ersichtlich woran das Ende der >> Sounddaten >> erkannt wird. > > Das EOF in der Sounddatei ist 0x80. Das muss für jeden Kanal einmal > vorkommen. Jeder Kanal wird, sobald er nicht mehr benötigt wird, durch > diesen Befehl beendet. Sobald ein Kanal beendet ist, werden für ihn > keine neuen Befehle gelesen. Wenn alle Kanäle beendet sind, stoppt sich > den Sequenzer selbst. > Der letzte Befehl in jeder Sounddatei muss 0x80 sein, damit der letzte > Kanal auch beendet wird. Sonst tritt deine Vermutung ein und der > Synthesizer spielt weiter ins Nirwana. AH! Jetzt kommt Licht in's Dunkle. Das habe ich aus den Sourcen nicht erlesen können. Anfängerfehler. Bei meinen Tests habe ich die konvertierten Midi-Dateien einfach irgendwo abgeschnitten, so dass sie noch in's Flash passen. Jetzt wird's langsam spannend. Vielen Dank!
Der Sound erinnert mich schwer an das, was wir vor 30 Jahren auf dem C-64 gemacht haben. Genau so klang es auf dem SID. Genau so. Der erste Sound rechts oben hupt so wie "summer games" :-)
Hier wurde erwähnt, dass es den Synth auch als C-Code gibt, habe hier im Thread nichts gefunden. Wo gibts die Files?
Sam .. schrieb: > http://www.mikrocontroller.net/articles/AVR-Synthesizer Hier gibt es einen C-Wrapper für den Assemblercode. Der Synth selbst ist nach wie vor in Assembler geschrieben und daher nur auf AVR bzw. der XMEGA Architektur lauffähig.
Sam .. schrieb: > Hier gibt es einen C-Wrapper für den Assemblercode. Es gab mal einen ASM to C - in der guten alten Zeit, als viel Assembler absorbiert wurde. Der Parser hat alle ASM-Befehle in C-Konstrukte übersetzt und damit konnte man den Compiler füttern. Nur bei den Registern und muss man aufpassen, dass man Prozessor-Register, RAM-Register und IO-Register richtig addressiert. Das müsste eigentlich leicht zu machen sein. Also zumindest von dem, der den Code kennt. Dann könnte man es auf einen I7 umsetzen oder einen Emulator nehmen und in einen VST-plugin bringen.
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.