Il y a quelques années, j'ai réalisé un réveil sur le microcontrôleur ATmega8, où j'ai implémenté un synthétiseur de mélodie simple à une seule tonalité (une seule voix). Il existe de nombreux articles sur Internet pour les débutants sur ce sujet. En règle générale, un minuteur 16 bits est utilisé pour générer la fréquence (notes), qui est configurée d'une certaine manière, forçant au niveau matériel à émettre un signal sous la forme d'un méandre sur une broche spécifique du MC. Le deuxième temporisateur (8 bits) est utilisé pour implémenter la durée d'une note ou d'une pause. Les notes selon des formules bien connues sont comparées aux fréquences, et elles sont à leur tour comparées à certains nombres à 16 bits, inversement proportionnels aux fréquences qui spécifient les périodes de comptage de la minuterie.
Dans ma conception, j'ai fourni trois mélodies écrites dans la même tonalité et la même échelle. J'ai donc dû utiliser un nombre limité et certain de notes, ce qui a facilité la modélisation. De plus, les trois morceaux ont été joués au même rythme. Le code de note et son code de durée tiennent facilement dans un octet. Le seul inconvénient de ce modèle était le manque de polyvalence, la capacité à éditer, remplacer ou compléter rapidement la mélodie. Pour enregistrer une mélodie, je l'ai d'abord esquissée dans un éditeur de musique sur un ordinateur, puis copié les notes et leur durée, avec la numérotation dont j'ai décidé à l'avance, puis formé les octets résultants. J'ai fait les dernières opérations en utilisant le programme Excel.
À l'avenir, je voulais éliminer l'inconvénient susmentionné, trahir le design d'une certaine universalité et réduire le temps de mise en œuvre de la mélodie. Il y avait une telle idée que le programme MK lisait les octets d'un des formats de musique célèbres. Le plus populaire et le plus courant est le format MIDI. Plus littéralement, ce n'est pas tant un format que toute une «science» qui peut être lue sur Internet. La spécification MIDI définit le protocole de transmission des messages en temps réel sur l'interface physique correspondante et décrit comment les fichiers midi sont organisés dans lesquels ces messages peuvent être stockés. Le format midi étant orienté musique, il trouve donc application dans le domaine concerné. Il s'agit d'un contrôle synchrone de l'équipement sonore, de la musique en couleur, des synthétiseurs musicaux et des robots, etc. Dans le domaine domestique, le format midi a été rencontré à l'époque du début du développement des téléphones portables. Dans ce cas, des messages concernant l'inclusion ou la désactivation d'une note particulière, des informations sur un instrument de musique, le volume des notes sonores, etc. sont enregistrés dans le fichier midi. Le téléphone portable qui lit un tel fichier contient un synthétiseur qui interprète les messages midi de ce fichier en temps réel et joue la mélodie. Au tout début, les téléphones ne pouvaient jouer que des mélodies à un seul ton. Au fil du temps, la soi-disant polyphonie est apparue.
Sur Internet, j'ai rencontré des articles sur l'implémentation d'un synthétiseur polyphonique sur MK, qui lit les fichiers midi. Dans ce cas, au moins, une «table d'ondes» préformée (une liste de formes d'ondes sonores) est utilisée pour chaque instrument de musique stocké dans la mémoire de MK. Et dans mon cas particulier, nous nous concentrerons sur la mise en œuvre d'un modèle plus simple: un synthétiseur à un seul ton (une seule voix).
Pour commencer, j'ai étudié attentivement la structure du fichier midi et suis arrivé à la conclusion qu'en plus des informations nécessaires sur les notes, il contient des informations redondantes supplémentaires. Par conséquent, il a été décidé d'écrire un programme simple pour convertir un fichier midi dans son propre format. Le programme, fonctionnant avec de nombreux fichiers MIDI, non seulement convertit les formats, mais les organise également d'une certaine manière. À l'avance, j'ai décidé d'organiser le stockage de nombreux morceaux dans la mémoire ROM (EEPROM 24XX512). Pour faciliter la visualisation dans l'éditeur HEX, je me suis assuré que chaque mélodie commence au début du secteur. Contrairement à une carte SD (par exemple), le concept de secteur n'est pas applicable à la ROM utilisée, je m'exprime donc conditionnellement. La taille du secteur est de 512 octets. Et le premier secteur du ROM est réservé aux adresses des secteurs des débuts de chaque mélodie. On suppose que la mélodie peut prendre plusieurs secteurs.
Une description complète du format de fichier midi, bien sûr, ne vaut pas la peine d'être faite ici. Je n'aborderai que les points les plus nécessaires et les plus nécessaires. Un fichier midi contient 16 canaux, ce qui correspond généralement à l'un ou l'autre instrument de musique. Dans notre cas, peu importe de quel type d’instrument il s’agit et un seul canal est nécessaire. Le contenu de chaque chaîne, ainsi que l'en-tête, est établi dans un fichier midi selon un principe très similaire à l'organisation du stockage des flux vidéo et audio dans un conteneur AVI. J'ai écrit sur ce dernier plus tôt dans l'un de mes articles. L'en-tête du fichier midi est un ensemble de certains paramètres. Un de ces paramètres est la résolution temporelle. Il est exprimé en nombre de «ticks» (une sorte de pixel) par trimestre (PPQN). Un quart est un laps de temps pendant lequel une noire est jouée. Selon le tempo de la mélodie, la durée du trimestre peut être différente. Par conséquent, la durée d'un «pixel» (période d'échantillonnage) dépend du tempo et du PPQN. Toutes les informations sur l'heure d'un événement sont déterminées avec précision pour cette durée.
De plus, l'en-tête contient le type de fichier MIDI (type 0 ou type 1) et le nombre de canaux. Sans entrer dans les détails, nous travaillerons avec le type 1, le nombre de canaux 2. Un fichier midi avec une mélodie à un seul ton, logiquement, contient un canal. Mais dans le fichier midi de «type 1», il y a, en plus du fichier principal, un autre canal «non musical» dans lequel sont enregistrées des informations supplémentaires qui ne contiennent pas de notes. Il s'agit des soi-disant métadonnées. Il n'est pas non plus nécessaire d'entrer dans les détails. La seule information dont nous avons besoin, c'est qu'il y a des informations sur le rythme, et dans un format inhabituel: microsecondes par trimestre. À l'avenir, il sera montré comment utiliser ces informations, avec PPQN, pour configurer le minuteur MK, qui est responsable du tempo.
Dans le bloc de canal principal avec des notes, nous ne sommes intéressés que par des informations sur les événements d'activation et de désactivation des notes. Un événement d'activation de note a deux paramètres: le numéro de note et le volume. Au total, 128 notes et 128 niveaux de volume sont fournis. Nous ne nous intéressons qu'au premier paramètre, car peu importe le volume de la note: toutes les notes lors de la lecture de la mélodie MK sonneront au même volume. Et, bien sûr, la mélodie ne doit pas contenir de notes «superposées», c'est-à-dire qu'à tout moment, plus d'une note ne doit pas retentir en même temps. Le code de l'événement de prise (d'activation) des notes est 0x90. Le code d'événement note off est 0x80. Cependant, au moins l'éditeur Cakewalk Pro Audio 9 n'utilise pas l'événement avec le code 0x80 lors de l'exportation de la composition au format midi. Au lieu de cela, l'événement 0x90 a lieu dans toute la partie musicale, et la note que la note est désactivée est son volume zéro. C'est-à-dire que l'événement «désactiver la note» est équivalent à l'événement «activer la note avec un volume nul». Peut-être que cela se fait pour des raisons d'économie. Selon la spécification, le code d'événement ne peut pas être réécrit si cet événement est répété. Entre les événements, les informations sur l'intervalle de temps sont enregistrées dans un format de longueur variable. Ce sont les valeurs entières du nombre de «ticks» mentionnés ci-dessus. Le plus souvent, un octet suffit pour enregistrer l'intervalle de temps. Si deux événements se succèdent, alors entre eux l'intervalle de temps est évidemment égal à zéro. Ceci, par exemple, désactive la première et l'inclusion de la deuxième note qui la suit, s'il n'y a pas de pause (espace) entre elles.
Essayons d'écrire une séquence de notes en utilisant le programme "Cakewalk Pro Audio 9". Il y a beaucoup de rédacteurs, mais je me suis installé sur le premier rencontré.

Vous devez d'abord configurer les paramètres du projet. Dans cet éditeur, vous pouvez définir la résolution dans le temps (PPQN). Je choisis la valeur minimale égale à 48. Une valeur trop grande n'a pas de sens, car vous devez travailler avec de grands nombres dépassant 1 octet. Mais la valeur minimale de 48 est tout à fait satisfaisante. Dans presque toutes les mélodies, les notes inférieures à 1/32 ne sont pas trouvées. Et si le nombre de «ticks» par trimestre est de 48, alors la note ou pause 1/32 aura une durée de 48 / (32/4) = 6 «ticks». Autrement dit, il existe une possibilité théorique de diviser complètement la note de 1/32 par 2, et même par 3. Nous laissons les paramètres restants dans la fenêtre des propriétés du projet par défaut.

Ensuite, ouvrez la propriété de la première piste et attribuez-lui un numéro de canal égal à 1. À votre goût, sélectionnez un patch qui correspond à un instrument de musique lorsque vous jouez une mélodie dans l'éditeur. Le numéro de patch, bien sûr, n'affectera pas le résultat final.

Le tempo de la mélodie est défini en nombre de quarts par minute dans la barre d'outils de l'éditeur. La valeur de tempo par défaut est 100 bpm.
Le microcontrôleur dispose d'une minuterie 8 bits qui, comme déjà mentionné, sera utilisée pour contrôler la durée des notes et des pauses. Il a été décidé que l'intervalle de temps entre les opérations adjacentes (interruptions) d'un tel temporisateur correspondrait à l'intervalle d'un «tick». Selon le tempo de la mélodie, la valeur de cet intervalle de temps sera différente. J'ai décidé d'utiliser des interruptions de temporisation de débordement. Et selon le paramètre initial d'initialisation du timer, il est possible d'ajuster ce même intervalle de temps, qui dépend du tempo de la mélodie. Passons maintenant aux calculs.
En règle générale, dans la pratique, en moyenne, le tempo des chansons se situe dans la plage de l'ordre de 50 à 200. Il a déjà été dit que le tempo du fichier midi est réglé en microsecondes d'un quart. Pour un tempo de 50, cette valeur est de 60 000 000/50 = 1 200 000, et pour un tempo de 250, elle sera de 240 000. Puisque, selon le projet, un quart contient 48 ticks, la longueur de tick pour le tempo minimum sera de 1 200 000/48 = 25 000 μs. Et pour le rythme maximum, si vous calculez de la même manière, - 5000 μs. Pour MK avec une fréquence de quartz de 8 MHz et un diviseur de minuterie préliminaire maximum de 1024, nous obtenons ce qui suit. Pour le rythme minimum, le chronomètre doit être calculé 25000 / (1024/8) = 195 fois. Le résultat est arrondi à la valeur entière la plus proche, l'erreur d'arrondi n'affecte pratiquement pas le résultat. Pour l'allure maximale - 5000 / (1024/8) = 39. Ici, l'erreur d'arrondi n'affecte pas d'autant plus qu'une valeur arrondie de 39 est également obtenue pour les valeurs de tempo voisines de 248 à 253. Par conséquent, le temporisateur doit être initialisé avec une valeur inverse: pour le tempo minimum - (256-195) = 61, et pour le maximum - (256 -39) = 217. Le rythme minimum auquel la minuterie sera fournie dans la configuration MK actuelle est de 39 bpm. Avec cette valeur, le temporisateur doit être compté 250 fois. Et avec une valeur de 38 - déjà 257, ce qui dépasse les limites de la minuterie. J'ai décidé de prendre la valeur de 40 bpm pour le rythme minimum et de 240 pour le maximum.
Pour calculer le nombre de ticks, une minuterie virtuelle basée sur ce qui précède sera utilisée. C'est le nombre de ticks qui définit la durée d'une note ou d'une pause, comme déjà mentionné ci-dessus.
Pour implémenter la lecture des notes, un deuxième minuteur 16 bits est utilisé. Selon la spécification MIDI, un total de 128 notes sont fournies. Mais en pratique, ils sont beaucoup moins utilisés. De plus, les notes des octaves les plus basses (avec des fréquences d'environ 50 Hz) et les plus hautes (avec des fréquences d'environ 8 kHz) ne seront pas reproduites harmonieusement par le microcontrôleur. Mais pour tout cela, une minuterie 16 bits avec un diviseur fixe couvre presque toute la gamme de notes fournies par midi, à savoir, sans les 35 premiers. Mais j'ai choisi comme début la note avec le chiffre 37 (son code est 36, puisque l'encodage vient de zéro). Ceci est fait par commodité, car ce nombre correspond à la note «C», comme la première note dans une échelle traditionnelle. Il lui correspond avec une fréquence de 65,4 Hz, et le demi-cycle est - 1 / 65,4 / 2 = 0,00764 sec. Cette période à une fréquence MK de 8 MHz et un diviseur 1 (c'est-à-dire sans diviseur) comptera le minuteur approximativement dans son ensemble pour 0,00764 / (1/8000000) = 61156 fois. Pour la 35e note, si vous comptez, cette valeur sera 68645, ce qui est au-delà de la plage du temporisateur 16 bits. Mais, même s'il était nécessaire de jouer des notes en dessous du 36e, vous pouvez entrer le premier diviseur de minuterie disponible, égal à 8. Mais cela n'est pas nécessaire en pratique, tout comme il n'y en a même pas pour jouer les notes les plus hautes. Néanmoins, pour la 128e note la plus élevée, la note «G» avec une fréquence de 12 543,85 Hz, la valeur de la minuterie est, si elle est comptée de manière similaire, 319. Les détails de tous les calculs ci-dessus sont déterminés par la configuration spécifique du mode minuterie, qui sera montrée plus loin.
Maintenant, j'ai une question non moins importante: comment obtenir la relation entre le numéro de note et le code de la minuterie? Il existe une formule bien connue pour calculer la fréquence d'une note par son numéro. Et le code de minuterie pour une fréquence connue est calculé facilement, comme indiqué ci-dessus dans les exemples. Mais la racine du 12e degré apparaît dans la formule de la dépendance de la fréquence à la note, et en général, je ne voudrais pas charger le contrôleur avec de telles procédures de calcul. D'un autre côté, la création d'un tableau de codes temporisés pour toutes les notes n'est pas non plus rationnelle. Et j'ai décidé de faire ce qui suit, en choisissant un terrain d'entente. Il suffit de créer un tableau de codes de temporisation pour les 12 premières notes, qui sont d'une octave. Et les notes des octaves suivantes doivent être obtenues en multipliant séquentiellement les fréquences des notes de la première octave par 2. Ou, la même chose, en divisant séquentiellement les valeurs des codes de minuterie par 2. Une autre commodité est que le nombre d'octaves, par coïncidence, est un argument dans le fonctionnement du décalage binaire vers la droite ( »), Qui servira d'opération de division par deux. Je n'ai pas choisi cet opérateur par hasard, car son argument reflète l'exposant de la puissance du diviseur (le nombre de divisions par 2). Et c'est le nombre d'octave. Pour mon jeu de notes, un total de 8 octaves est impliqué (la dernière octave est incomplète). Une note dans un fichier midi est encodée avec un octet, plus précisément, 7 bits. Pour jouer des notes en MK, selon l'idée ci-dessus, vous devez d'abord calculer le numéro d'octave et le numéro de note dans l'octave en utilisant le code de note. Cette opération est effectuée au stade de la conversion du fichier midi dans un format simplifié. Huit octaves peuvent être encodées en trois bits, et 12 notes dans une octave peuvent être encodées en quatre. Au total, il s'avère que la note est encodée dans les mêmes sept bits que dans le fichier midi, mais uniquement dans une représentation différente convenant à MK. En raison du fait que 16 bits peuvent être codés sur 4 bits et des notes dans une octave de 12, il y a des octets inutilisés.
Le dernier huitième bit peut être utilisé comme marqueur pour activer ou désactiver les notes. Dans le cas de MK, en raison de l'unanimité de la mélodie, les informations sur la note en sourdine seront redondantes. Avec un changement direct de note dans la mélodie, il n'y a pas un «turn-on-turn-on», mais un «interrupteur» de la note. Et en cas de pause, le «silence est activé», pour lequel vous pouvez sélectionner un octet spécial dans l'ensemble d'octets inutilisés et ne pas utiliser du tout les informations sur la désactivation de la note. Une telle idée est bonne en ce qu'elle enregistre la taille de la mélodie résultante après la conversion, mais complique généralement le modèle. Je n'ai pas suivi cette idée, car il y a déjà beaucoup de mémoire.
Les informations sur les notes de mélodie dans le fichier midi sont stockées dans le bloc du canal correspondant dans la vue "interval-event-interval-event ...". Dans le format converti, exactement le même principe s'applique. Pour enregistrer un événement (activer ou désactiver une note), comme mentionné ci-dessus, un octet est utilisé. Le premier bit (le bit le plus significatif 7) code le type d'événement. La valeur «1» est la note activée et la valeur «0» est la note désactivée. Les trois bits suivants codent le numéro d'octave et les quatre bits les plus bas codent le numéro de note dans l'octave. Un octet est également utilisé pour enregistrer l'intervalle de temps. Dans le format midi d'origine, un format de longueur variable est utilisé pour cela. Son petit inconvénient est que seuls 7 bits codent l'intervalle de temps (le nombre de «ticks»), et le huitième bit est un signe de continuation. Autrement dit, avec un octet, en fait, vous pouvez coder un intervalle allant jusqu'à 128 ticks. Mais comme les intervalles de temps entre les événements dans des mélodies réelles et simples dépassent parfois 128, mais ne dépassent presque jamais 256, j'ai abandonné le format de longueur variable et j'ai géré avec un octet. Il code un intervalle de temps allant jusqu'à 256 ticks. Comme le projet utilise 48 ticks par trimestre, ou 48 * 4 = 192 ticks par cycle, un octet peut être utilisé pour coder un intervalle de 256/192 = 1 durée. (3) (un tout et un tiers) cycles, qui assez.
Dans le format natif dans lequel le fichier midi est converti, j'ai également appliqué un petit en-tête de 16 octets. Les 14 premiers octets contiennent le nom de la mélodie. Naturellement, le nom ne doit pas dépasser 14 caractères. Vient ensuite un espace nul. Le dernier octet suivant reflète le tempo de la mélodie dans une vue pratique pour MK. Cette valeur est calculée au stade de la conversion et sert à initialiser le temporisateur MK, qui est responsable du rythme. Comment il est calculé est discuté dans quelques paragraphes ci-dessus.
À partir du 17e octet, le contenu de la mélodie suit. Chaque octet impair correspond à un intervalle de temps et chaque octet pair correspond à un événement (remarque). , , , . 0xFF. . , . , , , , . . 0x0F, . 16- , , 12. . , « », , . ( ). , 36 . , ( ) , .
«Cakewalk Pro Audio 9», . , . : «Piano roll» . . .


, () , . , , .
.

, , , . , , - , «Del». , , - «». , , . , , . , : .
« 1», .

HEX . , , avi ( ), , (big endian).

. . , (1), (2) (48). . . 6 , . 6 (- 0xFF) 0x51 0x03 . – . . , . . – – . , , , , . ( ) , 48*3=144 128. , . 144 . . , . . , () , : . , 0x90, . . – , 128 .
, , , , - EEPROM. , . HEX , . .

( 16 ), , . 0xC1 (193) 154, 155 156. , 155 bpm, . ( 14-), , . – «Classic». , HEX . , , , .
( 17- ) . , , . , , . , , /. , «» , 0xB4 0x34, 0x34, . 0xB4 (0b10110100) , , 0x34 (0b00110100) , . 0x34 : 0b011, – 0b0100. , , 3 4 . , , . . , Excel, 76 (0x4C) , E6 ( «» 6- ). : .
, . , , . , . , . . , . , , - , , , . , , , 1 . «» 1 , .
(0x90), 128, , . . , , . , 0xFF, , . , .
Considérez le tout premier secteur du fichier image EEPROM de sortie. Comme je l'ai déjà écrit, il sert de liste d'adresses de secteurs du début des mélodies. Le programme a réussi à numériser 8 morceaux sans erreur (au moment de la rédaction, j'avais enregistré 8 morceaux). La valeur du nombre de mélodies est enregistrée dans le dernier 512e octet du secteur. Et dès le tout début du secteur, des adresses sont écrites. Pour la première mélodie, l'adresse est 0x01, ce qui correspond au deuxième secteur (le premier, si vous comptez à partir de zéro). Les troisième et quatrième mélodies (deux sur huit) se sont avérées longues et ne cadraient pas dans un seul secteur. Par conséquent, des lacunes sont observées dans la séquence d'adresses. Si vous comptez, 64 Ko de mémoire, vous ne pouvez pas enregistrer plus de 127 morceaux, donc un secteur pour l'adressage suffit.
, , Excel. ( ).


, , . , . , , , .
1.cpp#include <stdio.h> #include <windows.h> #include <string.h> #define SPACE 1 HANDLE openInputFile(const char * filename) { return CreateFile ( filename, // Open Two.txt. GENERIC_READ, // Open for writing 0, // Do not share NULL, // No security OPEN_ALWAYS, // Open or create FILE_ATTRIBUTE_NORMAL, // Normal file NULL); // No template file } HANDLE openOutputFile(const char * filename) { return CreateFile ( filename, // Open Two.txt. GENERIC_WRITE, // Open for writing 0, // Do not share NULL, // No security OPEN_ALWAYS, // Open or create FILE_ATTRIBUTE_NORMAL, // Normal file NULL); // No template file } void filepos(HANDLE f, unsigned int p){ LONG LPos; LPos = p; SetFilePointer (f, LPos, NULL, FILE_BEGIN); //FILE_CURRENT //https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointer } DWORD wr; DWORD ww; unsigned long int read32(HANDLE f){ unsigned char b3,b2,b1,b0; ReadFile(f, &b3, 1, &wr, NULL); ReadFile(f, &b2, 1, &wr, NULL); ReadFile(f, &b1, 1, &wr, NULL); ReadFile(f, &b0, 1, &wr, NULL); return b3<<24|b2<<16|b1<<8|b0; } unsigned long int read24(HANDLE f){ unsigned char b2,b1,b0; ReadFile(f, &b2, 1, &wr, NULL); ReadFile(f, &b1, 1, &wr, NULL); ReadFile(f, &b0, 1, &wr, NULL); return b2<<16|b1<<8|b0; } unsigned int read16(HANDLE f){ unsigned char b1,b0; ReadFile(f, &b1, 1, &wr, NULL); ReadFile(f, &b0, 1, &wr, NULL); return b1<<8|b0; } unsigned char read8(HANDLE f){ unsigned char b0; ReadFile(f, &b0, 1, &wr, NULL); return b0; } void message(unsigned char e){ printf("Error %d: ",e); switch(e){ case 1: // - -; printf("In track0 event is not FF\n"); break; case 2: // - 127 printf("Len of FF >127\n"); break; case 3: // ; printf("Midi is incorrect\n"); break; case 4: // ; printf("Delta>255\n"); break; case 5: // RPN NRPN; printf("RPN or NRPN is detected\n"); break; case 6: // ; printf("Note in 1...35 range\n"); break; case 7: // ; printf("Long of name of midi file >18\n"); break; } system("PAUSE"); } int main(){ HANDLE in; HANDLE out; unsigned int i,j; unsigned int inpos; unsigned int outpos=0; unsigned char byte; // ; unsigned char byte1; // 1 ; unsigned char byte2; // 2 ; unsigned char status; //- ( ); unsigned char sz0; // -; unsigned long int bsz0; // -; unsigned short int format, ntrks, ppqn; // ; unsigned long int bsz1; // ; unsigned long int bpm; // ( . ); unsigned long int time=0; // ( ); unsigned char scale; // , ; unsigned char oct; // ; unsigned char nt; // ; unsigned char outnote; // ; unsigned char prnote=0; // ; unsigned char tdt; // () ; unsigned int dt; // ( ); unsigned int outdelta=0; // ( ); unsigned char prdelta=0; // ; char fullname[30]; // ; char name[16]; // ; WIN32_FIND_DATA fld; // mid; HANDLE hf; unsigned short int csz; // ; unsigned char nfile=0; // ; unsigned char adr[128]; // ; out=openOutputFile("IMAGE.out"); outpos=512; // ; filepos(out,outpos); hf=FindFirstFile(".\\midi\\*.mid",&fld); do{ printf("\n***** %s *****\n",fld.cFileName); if(strlen(fld.cFileName)>18){ // ; message(7); } sprintf(name,"%s",fld.cFileName); name[strlen(fld.cFileName)-4]=0; // ; sprintf(fullname,".\\midi\\%s",fld.cFileName); // ; WriteFile(out, name, strlen(name), &ww, NULL); // ; in=openInputFile(fullname); // ; #include "process.cpp" // ; outpos+=((csz/512)+1)*512; // ; adr[nfile]=(outpos/512)-((csz/512)+1); // () ; filepos(out,outpos); CloseHandle(in); nfile+=1; }while(FindNextFile(hf,&fld)); // , ; FindClose(hf); WriteFile(out, &outnote, 1, &ww, NULL); outpos=0; // ; filepos(out,outpos); WriteFile(out, adr, nfile, &ww, NULL); outpos=511; // ; filepos(out,outpos); WriteFile(out, &nfile, 1, &ww, NULL); CloseHandle(out); system("PAUSE"); return 0; }
Pièce jointe au fichier Process.cpp En fait, la partie de base du programme pour MK est très simple. Considérez l'une des options pour sa mise en œuvre, plus précisément, sa partie principale.
Le minuteur 1, utilisé pour générer le son des notes, est configuré comme suit. Pour activer et désactiver les notes, les substitutions suivantes sont utilisées, respectivement.
#define ENT1 TCCR1B=0x09;TCCR1A=0x40 #define DIST1 TCCR1B=0x00;TCCR1A=0x00;PORTB.1=0
Avant de démarrer la minuterie, vous devez attribuer au registre OCR1A une valeur de 16 bits qui correspondra à la fréquence en cours de lecture. Cela sera montré plus tard. Lorsque le temporisateur est activé, le registre TCCR1B se voit attribuer le «Mode de génération de forme d'onde» avec un diviseur de temporisateur de 1, et le registre TCCR1A est réglé sur «Basculer OC1A lors de la comparaison». Dans ce cas, le signal est supprimé de la sortie spécialement désignée de MK «OC1A». Dans l'ATmega8 du package SMD, il s'agit de la broche 13, qui est la même que PORTB.1. Lorsque la minuterie est désactivée, les deux registres sont réinitialisés et la sortie de PORTB.1 est forcée à zéro. Cela est nécessaire pour éviter, pendant le silence, la sortie d'une tension constante, ce qui serait indésirable pour l'entrée de l'ULF. Bien que, vous pouvez mettre un condensateur dans le circuit, mais vous pouvez également désactiver la sortie par programme. Une tension constante peut se produire sur cette sortie si la note est coupée au moment de la phase correspondante du signal, et ce dans 50% des cas.
Créez un tableau de valeurs de minuterie pour 12 notes de la toute première octave. Ces valeurs ont été calculées à l'avance.
freq[]={61156,57724,54484,51426,48540,45815,43244,40817,38526,36364,34323,32396};
Les notes des autres octaves, comme je l'ai dit, seront obtenues en divisant par deux degrés.
La configuration du temporisateur 0 est encore plus simple. Il fonctionne en permanence, avec une interruption de débordement, chaque fois étant à nouveau initialisé avec la valeur qui correspond au tempo de la mélodie. Le diviseur de minuterie est 5: TCCR0 = 0x05. Sur la base de cette minuterie, une minuterie virtuelle est créée qui compte les tics (temps) dans la mélodie. Le traitement de la réponse de ce temporisateur est placé dans le cycle de programme principal.
La fonction d'interruption du temporisateur 0 est la suivante.
interrupt [TIM0_OVF] void timer0_ovf_isr(void){ if(ent01){ vt01+=1; } TCNT0=top0; }
Ici, la variable ent01 est responsable de l'activation du temporisateur virtuel. Par cette variable, il peut être activé ou désactivé si nécessaire. La variable vt01 est la variable primaire dénombrable du temporisateur virtuel. La ligne TCNT0 = top0 indique l'initialisation du temporisateur 0 à la valeur souhaitée top0, qui est lue à partir du titre de la mélodie avant de la jouer.
Le numéro de la mélodie à jouer correspond à la variable alm. Il sert également de drapeau du début de la reproduction. Elle doit attribuer un numéro de mélodie de l'une des manières, selon la tâche. Après cela, le bloc suivant du cycle principal deviendra actif.
if(alm){
Un passage supplémentaire de note en note est effectué dans l'unité de traitement du temporisateur virtuel, qui est également placé dans la boucle principale.
if(vt01>=top01){
D'après les commentaires dans le texte du programme, tout devrait être assez clair et compréhensible.
Pour arrêter la mélodie, utilisez l'insertion suivante de la boucle principale.
if(stop){
Il y a une petite remarque sur l'implémentation de la lecture de la mélodie. Avant que chaque nouvelle note ne commence à retentir, le microcontrôleur passe un peu de temps à convertir l'octet de lecture de la note en une valeur de minuterie. Cette fois, comme il s'est avéré en pratique, est relativement petite et n'affecte pas la qualité de la lecture. Mais j'avais des doutes sur le fait que cette opération resterait invisible. Dans ce cas, des pauses supplémentaires apparaissent avant chaque note et le rythme de la mélodie est rompu. Mais ce problème est également résoluble. Il suffit de calculer à l'avance les valeurs de la minuterie de la note suivante pendant que la note actuelle retentit. Cette procédure doit être effectuée séparément du traitement du temporisateur virtuel dans la boucle de programme principale à l'aide d'un indicateur spécialement désigné. Étant donné que le temps de calcul ne dépassera probablement pas le temps de lecture de la note la plus courte, une telle solution est appropriée.
Passons maintenant à tester le programme.
En plus des extraits de code ci-dessus, j'ai ajouté des fonctions de traitement des boutons au programme MK, avec lesquelles je contrôle l'inclusion ou la désactivation d'une mélodie particulière. L'EEPROM est connectée à MK via le bus I2C, dont le travail est implémenté au niveau logiciel. Le projet a été réalisé avec l'aide de «CodeVisionAVR» avec «CodeWizardAVR». Je sort MK de la broche 13 vers la carte son du PC via le diviseur et j'enregistre le son de la mélodie dans l'éditeur de sons. J'ai flashé la mémoire EEPROM à l'aide du firmware, dont j'ai parlé dans l'un des articles précédents. Étant donné que tous les octets du fichier image ne sont pas utiles, le micrologiciel de mémoire ne peut être implémenté que par des octets utiles (jusqu'aux marqueurs de fin des mélodies) afin d'économiser du temps d'enregistrement et des ressources de puce. Pour ce faire, vous pouvez créer un programme distinct ou écrire des octets sur la puce directement pendant la conversion, en les ajoutant au programme principal.
Parmi les huit morceaux, il y en a trois de test, à l'aide desquels j'évaluerai la gamme de fréquences à l'oreille, le son de la fusion de notes identiques, le son des notes les plus courtes, les transitions rapides, etc. Permettez-moi de vous rappeler que la fusion des mêmes notes sonne en fait avec une pause d'un tick, et la première note de la fusion dure un tick de moins.
L'une des mélodies de test est une séquence de notes du premier au dernier d'une durée d'une note par quart et d'un tempo de mélodie de 40 bpm.

Dans ce scénario, une note sonne un peu plus d'une seconde, et vous pouvez donc écouter en détail le son de toute la gamme de notes. Sur le spectre de fréquences dans l'éditeur audio "Adobe Audition", les principales composantes de fréquence et leurs harmoniques supérieures sont observées en raison de la forme d'onde en dents de scie correspondante. Et la relation logarithmique entre le numéro de note et la fréquence est frappante.

En analysant les intervalles de temps, on voit clairement que la vraie pause entre des notes consécutives est en moyenne d'environ 145 échantillons (à une fréquence d'échantillonnage de l'enregistrement audio 44100 Hz), ce qui est d'environ 3 ms. C'est le temps pendant lequel le MK effectue les calculs nécessaires. Ces inserts sont présents régulièrement avant chaque note. J'ai spécifiquement écrit le sens dans les échantillons, car ces informations sont plus originales et plus précises, bien que ce ne soit pas très important.

Et la durée d'un tick à un rythme moyen de la mélodie de 120 bpm est d'environ 10 ms. Il s'ensuit qu'en principe, il serait possible de ne pas introduire la même correction en 1 tick, lorsque deux notes identiques se succèdent sans pause. Je pense qu'une insertion régulière de 3 ms entre les notes serait bien suffisante. Lors de l'écoute d'une mélodie, ces insertions régulières ne sont pas du tout perceptibles et les mélodies sonnent uniformément. Par conséquent, il n'est pas particulièrement nécessaire de calculer la valeur du minuteur pour la note suivante pendant la lecture de la note actuelle.
Une autre mélodie de test avec un tempo de 200 bpm contient successivement les mêmes notes 1/32 de la gamme moyenne sans pause. Dans ce cas, après le traitement, lors de la lecture entre eux, il y a une pause de 1 tick, ce qui à ce tempo rapide de 310 échantillons (environ 6 ms) du signal enregistré.

La durée de cette pause, en passant, est comparable à la période du signal, ce qui indique un tempo élevé de la mélodie. Et son son rappelle un trille.
En principe, cela peut être terminé. J'étais satisfait du résultat de l'appareil, il a dépassé toutes les attentes. La plupart du temps, je me suis consacré à l'étude du format midi et au débogage du programme de conversion. Je consacrerai également l'un des articles suivants à un sujet lié au MIDI, qui parlera de l'application de ce format dans d'autres applications intéressantes.