Ich habe jetzt seit ein paar Tagen damit zu kämpfen, und ich würde wirklich gerne mit meinem Projekt weitermachen. Das hält mich leider auf. Ich versuche, MIDI-Signale abzurufen, die über USB an das Arduino (Uno) gesendet werden, sie zu dekodieren und dann eine nicht native Tonbibliothek ( https://code.google.com/p/rogue-code/wiki /ToneLibraryDocumentation), um mehrere Rechteckwellentöne gleichzeitig zu spielen. Ich habe noch 6 E / A-Ports auf meinem Arduino, mit denen ich arbeiten kann, also erwarte ich, dass mich das auf 6 Töne beschränkt. Ich habe verschiedene Methoden ausprobiert, um dies zum Laufen zu bringen, konnte aber keine davon beenden, weil ich auf die eine oder andere Weise auf eine Straßensperre gestoßen bin. Meine Hoffnung ist es, mehrere Töne zu spielen, unabhängig davon, ob sie vom selben Kanal (Akkorde) oder von verschiedenen Kanälen (getrennte Instrumente) stammen, aber ich finde immer noch keinen guten Weg, dies zu tun.
Das Einlesen der MIDI-Daten ist nicht der schwierige Teil. Ich habe den folgenden Code, der von Greg Kennedy geschrieben und in den Arduino-Foren ( http://forum.arduino.cc/index.php?topic=79326.0 ) veröffentlicht wurde , um Daten von einem bestimmten MIDI-Kanal abzurufen und mit dem nativen abzuspielen Tone () Funktion, aber leider kann es nur eine Note auf einmal spielen.
// A very simple MIDI synth.
// Greg Kennedy 2011
#include <avr/pgmspace.h>
#define statusLed 13
#define tonePin 7
// MIDI channel to answer to, 0x00 - 0x0F
#define myChannel 0x00
// set to TRUE and the device will respond to all channels
#define respondAllChannels false
// midi commands
#define MIDI_CMD_NOTE_OFF 0x80
#define MIDI_CMD_NOTE_ON 0x90
#define MIDI_CMD_KEY_PRESSURE 0xA0
#define MIDI_CMD_CONTROLLER_CHANGE 0xB0
#define MIDI_CMD_PROGRAM_CHANGE 0xC0
#define MIDI_CMD_CHANNEL_PRESSURE 0xD0
#define MIDI_CMD_PITCH_BEND 0xE0
// this is a placeholder: there are
// in fact real midi commands from F0-FF which
// are not channel specific.
// this simple synth will just ignore those though.
#define MIDI_CMD_SYSEX 0xF0
// a dummy "ignore" state for commands which
// we wish to ignore.
#define MIDI_IGNORE 0x00
// midi "state" - which data byte we are receiving
#define MIDI_STATE_BYTE1 0x00
#define MIDI_STATE_BYTE2 0x01
// MIDI note to frequency
// This isn't exact and may sound a bit detuned at lower notes, because
// the floating point values have been rounded to uint16.
// Based on A440 tuning.
// I would prefer to use the typedef for this (prog_uint16_t), but alas that triggers a gcc bug
// and does not put anything into the flash memory.
// Also note the limitations of tone() which at 16mhz specifies a minimum frequency of 31hz - in other words, notes below
// B0 will play at the wrong frequency since the timer can't run that slowly!
uint16_t frequency[128] PROGMEM = {8, 9, 9, 10, 10, 11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 19, 21, 22, 23, 24, 26, 28, 29, 31, 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, 65, 69, 73, 78, 82, 87, 92, 98, 104, 110, 117, 123, 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 4186, 4435, 4699, 4978, 5274, 5588, 5920, 5920, 6645, 7040, 7459, 7902, 8372, 8870, 9397, 9956, 10548, 11175, 11840, 12544};
//setup: declaring iputs and outputs and begin serial
void setup() {
pinMode(statusLed,OUTPUT); // declare the LED's pin as output
pinMode(tonePin,OUTPUT); // setup tone output pin
//start serial with midi baudrate 31250
// or 38400 for debugging (eg MIDI over serial from PC)
Serial.begin(31250);
// indicate we are ready to receive data!
digitalWrite(statusLed,HIGH);
}
//loop: wait for serial data
void loop () {
static byte note;
static byte lastCommand = MIDI_IGNORE;
static byte state;
static byte lastByte;
while (Serial.available()) {
// read the incoming byte:
byte incomingByte = Serial.read();
// Command byte?
if (incomingByte & 0b10000000) {
if (respondAllChannels ||
(incomingByte & 0x0F) == myChannel) { // See if this is our channel
lastCommand = incomingByte & 0xF0;
} else { // Not our channel. Ignore command.
lastCommand = MIDI_IGNORE;
}
state = MIDI_STATE_BYTE1; // Reset our state to byte1.
} else if (state == MIDI_STATE_BYTE1) { // process first data byte
if ( lastCommand==MIDI_CMD_NOTE_OFF )
{ // if we received a "note off", make sure that is what is currently playing
if (note == incomingByte) noTone(tonePin);
state = MIDI_STATE_BYTE2; // expect to receive a velocity byte
} else if ( lastCommand == MIDI_CMD_NOTE_ON ){ // if we received a "note on", we wait for the note (databyte)
lastByte=incomingByte; // save the current note
state = MIDI_STATE_BYTE2; // expect to receive a velocity byte
}
// implement whatever further commands you want here
} else { // process second data byte
if (lastCommand == MIDI_CMD_NOTE_ON) {
if (incomingByte != 0) {
note = lastByte;
tone(tonePin,(unsigned int)pgm_read_word(&frequency[note]));
} else if (note == lastByte) {
noTone(tonePin);
}
}
state = MIDI_STATE_BYTE1; // message data complete
// This should be changed for SysEx
}
}
}
Dieser Code funktioniert hervorragend für monophones Audio, aber ich muss wirklich polyphone Töne erzeugen. Ich war schon immer eher ein PIC-Typ, also ist Arduino noch ziemlich neu für mich. Danke Leute!
BEARBEITEN: Nur um es klar zu sagen, ich möchte einen Mehrton-MIDI-Eingang in einen Mehrton-Rechteckwellenausgang umwandeln, den ich als Unterbrechersignal für einen anderen Teil des Projekts verwenden werde.
Polyphone Musik zu spielen, indem für jede "Stimme" separate Schaltungen verwendet werden, funktioniert sehr gut, wenn Musik logisch in eine Anzahl von Einzelstimmenkanälen unterteilt wird. In vielen Fällen ist es nicht nur egal, ob die Hardware-Stimmen nicht perfekt aufeinander abgestimmt sind – man möchte vielleicht sogar, dass einige von ihnen etwas lauter sind als andere.
Bei der Verwendung eines polyphonen Instruments zur Wiedergabe von MIDI-Daten ist es jedoch wichtig sicherzustellen, dass sich alle Stimmen gleich verhalten, und der einfachste Weg, dies zu erreichen, besteht darin, dieselben Schaltkreise zu verwenden, um sie alle zu implementieren, typischerweise mit einer Art Wavetable-Generierung .
Es gibt viele Möglichkeiten, Wavetable-basierte Musik zu spielen, die einen Kompromiss zwischen Rechenleistung und CPU-Zeit und Speicheranforderungen eingeht. Ich habe einen vierstimmigen Wavetable-Generator für den 6502 geschrieben, der zwölf Anweisungen (46 Zyklen) für jedes Ausgangssample verwendete [durchschnittlich nur drei pro Stimme!], aber dafür Datentabellen im Wert von fast 3K benötigte. Ich habe auch einen 8-stimmigen Wavetable-Generator für einen 10-MHz-PIC und einen 16-stimmigen für den PSOC geschrieben. Ich habe nicht viel mit dem Arduino gemacht, aber ich würde vermuten, dass ein achtstimmiger Wavetable-Synthesizer funktionieren würde.
Bei der Erstellung eines Wavetable-Synthesizers ist jedoch eine wichtige Sache zu beachten: Rechteckwellen klingen oft schlecht, wenn das Timing auf etwas quantisiert wird, das kein Vielfaches ihrer Frequenz ist. Wenn Sie saubere Rechteckwellen für ein MIDI-Keyboard mit fünf Oktaven erzeugen möchten, müssen Sie möglicherweise eine ziemlich hohe Abtastrate verwenden. Wenn Sie stattdessen glattere Wellen erzeugen möchten, können Sie mit einer niedrigeren Abtastrate auskommen.
Ich habe das Arduino nicht verwendet, daher weiß ich nicht, welche Art von Konstrukten in C den besten Code ergeben würden, aber eine typische Wavetable-Implementierung auf dem ARM würde so aussehen:
uint32_t freq[8],phase[8];
int32_t volume[8];
int8_t *table[8]; // Pointer to 256-byte array
int32_t total;
total = 0;
phase[0] += freq[0]; total += table[0][phase[0] >> 8]*volume[0];
phase[1] += freq[1]; total += table[1][phase[1] >> 8]*volume[1];
...
phase[7] += freq[7]; total += table[7][phase[7] >> 8]*volume[7];
Nach dem obigen Code total
wird ein Wert gespeichert, der (falls erforderlich) skaliert und entweder an einen DAC ausgegeben oder zum Einstellen eines PWM-Tastverhältnisses verwendet werden kann.
Der obige Code geht davon aus, dass die Wavetable genau 256 Bytes lang ist; andere Ansätze können verwendet werden, wenn dies nicht der Fall ist. Wenn der Arduino die Multiplikation mit nicht effizient durchführen kann volume
, ist es möglich, für jede Lautstärke eine andere Tabelle zu verwenden (mein 6502-Code verwendete zwei Sätze von Tabellen - einen für "laut" und einen für "leise").
Eine zusätzliche Überlegung, wenn Sie einen DAC verwenden (weniger zutreffend, wenn Sie einen PWM verwenden), ist, dass es hilfreich sein kann, einen Interrupt zu haben, anstatt alle für jede Welle generierten Einzelwerte zusammenzufassen und als Gruppe auszugeben geschieht achtmal so schnell und gibt jeweils den Wert für eine Welle aus. Dadurch werden effektiv drei weitere Bits an ADC-Präzision gewonnen. Wenn Sie Sinuswellen möchten und Ihre Hardware keine schnelle Multiplikation unterstützt, ist das Erzeugen von zwei Sinuswellen mit voller Amplitude, deren Frequenzen übereinstimmen, aber deren Phasen sich unterscheiden, gleichbedeutend mit dem Erzeugen einer Sinuswelle, deren Phase der Durchschnitt der beiden vollen ist -starke, und deren Amplitude proportional zur Größe "Eins plus Kosinus der Phasendifferenz" ist. Mein PIC-basierter Wellenformgenerator hat diesen Trick verwendet.
Ich bin auf einem Tablet, also muss ich mich kurz fassen. Um mehrere Töne zu haben, benötigen Sie eine Struktur für jede Stimme in Ihrem polyphonen Instrument, die die Ton-MIDI-Notennummer enthält, eine Konstante, die den Ausgangspin enthält, einen "Phasenakkumulator" für den Ton und ein Winkelinkrement für die Phase Akkumulator. Sie initialisieren diese Strukturen dann mit den gewünschten Ausgangspins und anderen leeren Feldern und speichern sie in einer Art Datenstruktur (ein Stapel wäre gut). Dies ist dann der "unbenutzte Notenstapel".
Wenn eine MIDI-Note-On-Nachricht eingeht, rufen Sie eine Funktion auf, um eine unbenutzte Note aus dem Stapel zu entfernen, und verwenden die Notennummer und eine Nachschlagetabelle, um das entsprechende Winkelinkrement für den Phasenakkumulator zu finden. Dann wird die ausgefüllte Struktur in einen leeren Slot in einem Array namens "Spielnoten-Array" eingefügt. Wenn eine Note-Off-Nachricht eingeht, wird eine Funktion aufgerufen, die das Array durchsucht, um die ausgeschaltete Note zu finden, und die Struktur aus dem Array entfernt und sie zurück in den unbenutzten Notenstapel legt.
Es wird auch einen Timer-Interrupt geben, der mit einer bestimmten Rate aufgerufen wird, abhängig von der Rechteckwelle mit der höchsten Ausgangsfrequenz, die Sie benötigen. Die Inkremente des Phasenakkumulators werden basierend auf dieser Rate berechnet. In der Unterbrechung wird das Array von Spielnoten durchsucht und alle Phasenakkumulatoren aktualisiert. Um die Rechteckwellen zu erzeugen, werden die Ausgangspins niedrig gesetzt, wenn das oberste Byte des jeweiligen Phasenakkumulators kleiner als 128 ist, hoch, wenn es größer ist.
Bearbeiten: Um die Dinge schneller zu machen, könnten Sie zur Laufzeit einfach eine Reihe von Strukturen für jede Stimme mallocieren. Verwenden Sie dann die Zeiger, anstatt sich um die Strukturen selbst zu bewegen.
Ignacio Vazquez-Abrams
DerStrom8
Ignacio Vazquez-Abrams
DerStrom8
Ignacio Vazquez-Abrams
DerStrom8
Jarrod Christmann
DerStrom8
DerStrom8