Posamezne koščke smo že nakazali, zdaj pa končno končajmo projekt.

V starejšem blogih si lahko preberete, kako iz Arduina spraviti resen zvok in kako na Arduina prključimo limono. Tule bomo opisali, kako na Arduina priključiti Midi – točneje, MP3 predvajalnik s čipom VS1053, ki zna sprejemati tudi ukaze Midi.

Predvajalnik dobimo, kot običajno, pri Kitajcih. Stati bi moral slabih 7-10 dolarjev. Imeti vse pine v spodnji tabelici. Nekateri znajo brati tudi kartice SD in imajo še kup dodatnih pinov. Ti ne motijo, vendar jih tule ne bomo potrebovali

# Žice

Predvajalnik priključimo takole.

Midi Arduino Midi Arduino
XCS 6 MOSI (ali SI) 11
XDCS 7 MISO (ali SO) 12
RST (ali RS, XRST) 8 SCK 13
DREQ 9 5 V Vcc
  GND GND

Nekateri imajo tudi vhod za napajanje s 3 V. Uporabimo ga lahko namesto 5 V, lahko pa ga izpupstimo.

MOSI, MISO in SCK morajo biti na 11, 12, in 13; ostale lahko prestavimo drugam, vendar je potem potrebno ustrezno spremeniti datoteko midi.h, konkretno, vrstice #define VS_XCS 6 in tiste, ki ji sledijo.

# Programiranje

MIDI deluje takole: ima 16 kanalov, oštevilčenih od 0 do 15. Vsakemu kanalu (oz. tistim, ki jih bomo uporabljali) določimo inštrument. Inštrumenti so oštevilčeni od 1 in 128, kot kaže tabela. Zdaj na kanalih prižigamo in ugašamo tone. Vsakem kanal lahko praviloma igra več tonov naenkrat, in naenkrat je lahko aktivnih več kanalov. Praviloma pa zato, ker imajo različni čipi (ali programi) različne omejitve, pa tudi inštrumenti so lahko bolj ali manj kvalitetni. Čip, s katerim se igramo mi, ni vrhunec tehnike, a za nas popolnoma dovolj.

Snemite datoteko midi.zip. V njej sta midi.h in midi.cpp; skopirajte ju v direktorij, v katerem bo program ali pa znotraj direktorija sketches/libraries (direktorij sketches je verjetno v direktoriju documents) naredite poddirektorij midi in ju skopirajte vanj. Datoteki sta pripravljeni na osnovi drugih knjižnic, a poenostavljeni za našo rabo.

Če na začetek programa dodamo #include <midi.h>, lahko v programu uporabljamo štiri funkcije za delo z MIDIjem.

Z init_midi() pripravimo MIDI. Pokličemo ga v začetku, v setup-u.

S setInstrument(kanal, instrument) nastavimo inštrument (1-128) za podani kanal (0-15). Z recimo setInstrument(0, 20) uredimo, da se bo kanal 0 oglašal z zvokom orgel.

noteOn(kanal, nota, hitrost) sproži predvajanje podane note na podanem kanalu. Kanal je spet število med 0 in 15; inštrument, s katerim se bo oglašal ta kanal, smo določili prej. Note so oštevilčene tako, da je 60 srednji C, odtod pa za vsak polton ali cel ton prištejemo 1. Cis je 61, D 62, Dis 63, E 64, F 65. Spet si lahko pomagamo s tabelo. Argument hitrost (0-127) pove, kako močno pritisnemo “tipko” - če bi ta inštrument igrali na klaviaturo. Hitrost lahko tudi izpustimo; v tem primeru bo imela vrednost 127.

noteOff(kanal, nota, hitrost): ton bo zvenel, dokler ga ne ugasnemo s funkcijo noteOff, z ustreznim kanalom in noto. Pri nekaterih inštrumentih (kitara, bobni) bo sicer izzvenel že prej, pri drugih (flavta, violina), pa bo ton igral, dokler ga ne ugasnemo. Argument hitrost pove hitrost, s katero izpustimo tipko. Pomen je odvisen od inštrumenta (in čipa oz. programa). Ta argument lahko spet izpustimo.

Ton lahko ustavimo tudi tako, da ga ponovno sprožimo s hitrostjo 0. Torej, toneOff(0, 60) je isto kot toneOn(0, 60, 0).

Zdaj se pa poigrajmo s temi funkcijami:

#include <midi.h>

void setup() {
    init_midi();

    setInstrument(0, 20);  // Orgle na kanalu 0
    noteOn(0, 60);  // C
    delay(1000);

    noteOn(0, 62);  // D (poleg C, saj ga nismo ustavili)
    delay(1000);

    noteOff(0, 62); // ustavimo D
    noteOn(0, 64);  // E (poleg C)
    delay(1000);

    noteOn(0, 65);  // F (poleg C in E)
    delay(1000);

    noteOff(0, 65); // ustavimo F
    noteOn(0, 67);  // G (poleg C in E)
    delay(1000);

    noteOff(0, 67); // ustavimo vse tri
    noteOff(0, 64);
    noteOff(0, 60);
}

void loop() {
}

Takole seveda ne bomo prišli daleč. Če že hočemo zaigrati kako melodijo, dajmo ustrezne note v tabelo. Tale košček programa vstavimo pred zadnje tri vrstice gornjega, pa bomo slišali dva “inštrumenta” hkrati.

char melodija[] = {72, 71, 67, 69, 64, 65, 67};

setInstrument(1, 53);  // Instrument, ki oponaša petje samoglasnika A
noteOn(1, 72);
delay(1000);
for(int i = 0; i < 6; i++) {
    noteOff(1, melodija[i]);
    noteOn(1, melodija[i+ 1]);
    delay(1000);
}
noteOff(1, 67);

# Orgle

Naredimo klaviaturo. Ker nam je MIDI pobral večino digitalnih pinov in ker se nam ne igra s čipi, ki omogočajo, da na dva pina priključimo poljubno število vhodov, bomo tipke priključili na analogne vhode. Uporabimo lahko običajne tipke (s pull-up upori) ali, če hočemo narediti reč zabavno, na Arduina prključimo limono. Program bo v obeh primerih enak.

Predpostavili bomo, da imamo osem limon priključenih na A0 – A7. Običajen Arduino Uno ima le šest analognih vhodov, torej bo šlo nanj le šest tipk (ali limon). Za celo oktavo potrebujemo, na primer, Arduino Nano ali pa kako kitajsko različico Arduina z osmimi analognimi vhodi.

#include <midi.h>

// Tabela, v katero beležimo trenutno pritisnjene tipke
bool pritisnjene[] = {false, false, false, false, false, false, false, false};

// Toni, ki ustrezajo tipkam (v srednji oktavi)
char toni[] = {60, 62, 64, 65, 67, 69, 71, 72};

void setup() {
    init_midi();           // pripravi MIDI
    setInstrument(0, 20);  // orgle
}

void klaviatura() {
    for(int i = 0; i < 8; i++) {                // za vseh 8 tipk
        bool pritisnjena = analogRead(i) < 500; // je pritisnjena?
        if (pritisnjena != pritisnjene[i]) {    // ce je stanje drugacno kot prej
            noteOn(0, toni[i], 127 * pritisnjena); // hitrost bo 0 ali 127
            pritisnjene[i] = pritisnjena;       // shranimo novo stanje tipke
        }
    }
}

void loop() {
    klaviatura();
}

Čisto preprosto, ni?

V tabeli pritisnjene imamo v začetku osem false-ov: nobena tipka ni pritisnjena. V klaviatura za vseh osem tipk (i gre od 0 do 7) pomerimo napetost na pripadajočem analognem vhodu. Če je manjša od 2.5 V (500), je tipka pritisnjena. Spremenljivka pritisnjena bo torej true ali false. Primerjamo jo z elementom tabele, ki ustreza tej tipki (pritisnjene[i]). Če se razlikujeta, je ta, ki igra naše orgle (učeno bi se mu reklo organist), pravkar pritisnil ali spustil tipko. To sporočimo Arduino tako, da sprožimo ton, ki pripada tej tipki (toni[i]) s hitrostjo 127 * pritisnjena. Ker je pritisnjena true ali false, kar je isto kot 1 ali 0, bo 127 * pritisnjena enako 127 ali 0. Če ton sprožimo s hitrostjo 0, ga s tem ustavimo. Na koncu le še zabeležimo novo stanje tipke v tabelo pritisnjene.

# Menjavanje inštrumentov

Dodajmo še možnost menjavanja inštrumentov. Uporabili bomo štirimestni sedemsegmentni zaslon s čipom TM1637, ki smo ga že opisovali v enem prejšnjih blogov. CLK bomo priključili na pin 4, DIO na 5. Uporabili bomo knjižnico TM1637Display; ta, ki jo opisujemo v omenjenem blogu je sicer boljša, vendar ni javno objavljena. Pa bodimo tule splošnejši.

Poleg tega zdaj potrebujemo dve tipki, s katerimi bomo menjali inštrumente. Priključili ju bomo na pina 2 in 3.

#include <TM1637Display.h>
#include <midi.h>

// Zaslon, ki kaze stevilko instrumenta
TM1637Display zaslon(4, 5);

// Trenutno izbrani instrument
char instrument = 20;

// Tabela, v katero beležimo trenutno pritisnjene tipke
bool pritisnjene[] = {false, false, false, false, false, false, false, false};

// Toni, ki ustrezajo tipkam (v srednji oktavi)
char toni[] = {60, 62, 64, 65, 67, 69, 71, 72};


void setup() {
    // Tipki za spreminjanje instrumentov
    pinMode(2, INPUT_PULLUP);
    pinMode(3, INPUT_PULLUP);

    // Vkljuci zaslon, pokazi trenutni instrument
    zaslon.setBrightness(7);
    zaslon.showNumberDec(instrument, true, 4, 0);

    // Pripravi midi, nastavi zacetni instrument
    init_midi();
    setInstrument(0, instrument);
}


void tipka(char pin, char spr) {
    // Funkcija spremeni trenutni instrument, ce je pritisnjena ustrezna tipka
    //
    // pin: pin, na katerem je tipka
    // spr: za koliko ta tipka spremeni instrument (+1 ali -1)
    if (digitalRead(pin) == LOW) {
        instrument += spr;
        if (instrument == -1) {                       // ce pridemo cez zadnjega, gre od zacetka
            instrument = 128;
        }
        else if (instrument == 128) {                 // in obratno
            instrument = 1; 
        }
        delay(200);
        zaslon.showNumberDec(instrument, true, 4, 0); // pokazi trenutni instrument
        setInstrument(0, instrument);                 // sporoci zamenjavo MIDIju
    }
}

char oktava(char instrument) {
    // Funkcija, ki vrne "obicajno" oktavo za podani instrument
    // Bas je potrebno igrati kake tri oktave nizje, violino pa oktavo ali
    // dve visje, da zvenita kot bas in kot violina.
    // Spreminjamo le (nekatere) instrumente med 33 in 80.
    //
    // instrument: stevilka instrumenta
    // rezultat: oktava (-3, -2, 0, 1 ali 2)
    int oktave[] = {-3, -3, -3, -3, -3, -3, -3, -3,  // 33 -- 40
                    +1,  0, -1, -3, +1, +1,  0, -2,  // 41 -- 48
                     0,  0,  0,  0,  0,  0,  0,  0,  // 49 -- 56
                     0,  0, -2,  0, -1,  0,  0,  0,  // 57 -- 63
                    +1,  0, -1, -2, +1, -1, -1,  0,  // 64 -- 72
                    +2, +1, +1, +1, +1,  0,  0,  0,  // 73 -- 80
                    };
    if ((instrument >= 33) && (instrument <= 80)) { 
        return oktave[instrument - 33];
    }
    return 0;
}

void klaviatura() {
    for(int i = 0; i < 8; i++) {                // za vseh 8 tipk
        bool pritisnjena = analogRead(i) < 500; // je pritisnjena?
        if (pritisnjena != pritisnjene[i]) {    // ce je stanje drugacno kot prej
            noteOn(0, toni[i] + 12 * oktava(instrument), 127 * pritisnjena);
            pritisnjene[i] = pritisnjena;       // shranimo novo stanje tipke
        }
    }
}

void loop() {
    // V zanki le preverjamo tipki za spremembo instrumenta
    // in tipke klaviature
    tipka(2, 1);
    tipka(3, -1);
    klaviatura();
}

V program smo dodali funkcijo tipka, ki kot argument prejme številko tipke (2 ali 3) in ali ta tipka poveča ali zmanjša številko inštrumenta za 1. Funkcija preveri, ali je tipka pritisnjena in v tem primeru spremeni inštrument — to sporoči MIDIju, poleg tega pa zamenja številko na zaslonu.

Za boljši zvok smo dodali še funkcijo oktava, ki pove, kakšna je “običajna” oktava za podani inštrument. Violino je potrebno igrati par oktav višje kot bas kitaro, če želimo, da zveni realistično.