
syl20dies
|
Bonjour post tres ancien et accepté mais j'y suis tombé dessus parcque ca m'interessait pour une appli que je souhaite dévellopper,donc je pense que c'est le meilleur endroit pour donner ma contribution.
voilà mon but est de créer un algo pour composer de la musique à partir de l'analyse de gamme, accords intervalles et toutes notions de théorie musicale, bref vaste projet, mais bon voila avant d'en arriver là la premiere question que je me suis posé c'est que va sortir mon algo pour que je puisse voir(entendre plutot) le résultat. j'ai un piano numerique et quelques soft d'edition midi, mais voila impossible de saisir une liste complete d'evenements de notes autrement que note par note je me suis donc dis "et bien je croie que je vais pas avoir le choix que de générer mes propres fichiers midi contenant toutes les notes issues de mon algo". je m'y suis donc collé et je vous propose donc ce code pour ecrire un fichier midi à partir d'un tableau de notes telles qu'on peut les trouver dans la vue liste d'evenement d'un sequenceur midi. c'est une version tres simpliste dans mon cas puisque je ne traite que de note/on note/off velocity-in et placage temporel tres sommaire(16ieme de note au plus fin) mais ca peut donner des idées.
1) formatage du fichier midi extrait légerement modifié inspiré de http://jedi.ks.uiuc.edu/~johns/links/music/midifile.html à voir pour plus de detail sur le codage d'un fichier midi que j'ai ici simplifié à outrance pour ne garder que ce qui m'interessait : les note/on et note/off
4D 54 68 64 MThd 00 00 00 06 chunk length 00 00 format 0 00 01 one track 00 60 96 per quarter-note
Then, the track chunk. Its header, followed by the events:
4D 54 72 6B MTrk 00 00 00 3B chunk length (44) Delta-time(var-length hex) Event Comments 00 90 30 60 90:noteon ,30(hex)=48(dec) = +48 semitones from C0=C4, 60 hex val for velocity in 60 90 43 40 60 90 4C 20 81 40 80 30 40 two-byte delta-time,80:noteoff ,30(hex)=48(dec) = +48 semitones from C0=C4, 40 hex val for velocity off 00 80 43 40 00 80 4C 40 00 FF 2F 00 //fin de track
2) le code
<? function completechaine($pstr,$d){ //formate la chaine $pstr en placant des "0" devant pour obtenir la longueur minimale donnée par $d //utile pour obtenir un hexadecimal inferieur à 16(dec) sur 2 charactères //ex :dechex(15)retourne F, //completechaine(dechex(15),2) retourne "0F" $newstr=$pstr; while(strlen($newstr)<$d){$newstr="0".$newstr;} return $newstr; }
function varlen($hex){ //retourne une chaine hexadécimale sous forme de valeur à longueur variable (VLK) cf http://en.wikipedia.org/wiki/Variable_length_unsigned_integer $a=decbin(hexdec($hex)); $nbbytes=ceil(strlen($a)/7); $a=completechaine($a,$nbbytes*7); $mywords=array($nbbytes); for($i=0;$i<$nbbytes;$i++){ $mywords[$i]=(($i<$nbbytes-1)?"1":"0").substr($a,$i*7,7); } $result=implode($mywords); return completechaine(dechex(bindec($result)),2); }
function findhexnote($strnote){ //retourne la valeur hexadécimale de la hauteur d'une note donnée en parametre $notedecal=array("C"=>0,"D"=>2,"E"=>4,"F"=>5,"G"=>7,"A"=>9,"B"=>11,"C#"=>1,"D#"=>3,"F#"=>6,"G#"=>8,"A#"=>9,"B"=>11); $decdies=intval(($strnote[1]=="#"))+1;//valeur booléenne entiere augmentée de 1 (si "#" : 2 sinon 1); return completechaine(dechex($notedecal[substr($strnote,0,$decdies)]+intval(substr($strnote,$decdies,strlen($strnote-$decdies)))*12),2); //multiplie le nombre de l'octave(souschaine de $strnote position 2 à fin si #, sinon position 1 à fin) par 12 et ajoute le decalage de la note dans l'octave, puis converti le tout en valeur hexadecimale sur 2 characteres }
function convertnotes($mesnotes){ //pour obtenir le code sous la forme [decalage temporel(VLK)][noteon/noteoff(90/80)][hauteur note(hex)][velocité(hex)] à partir d'un tableau de note sous la forme : // [note(string)][velocité(dec 0 -127)][time(M:1/4note:1/16note)][duree(M:1/4note:1/16note)] $hexanotes=""; $timepassed=0; $evts=array(count($mesnotes)*2);//initialise un tableu de longueur double à celui des notes donnée en parametre de sorte à obtenir un evenement note on et un evenenment noteoff for($i=0;$i<count($mesnotes);$i++){ $tdeb=split(':',$mesnotes[$i][2]); $tdur=split(':',$mesnotes[$i][3]); //convertion de la chaine temps de la note en entier(96 batements par quart de note tel que defini dans l'entete du fichier midi); $evts[$i*2]=$tdeb[0]*96*4+$tdeb[1]*96+$tdeb[2]*96/4; //convertion de temps + duree en temps absolu entier comme precedement correspondant au temps absolu de l'evenement note off; $evts[$i*2+1]=(intval($tdeb[2])+intval($tdur[2]))*96/4+(intval($tdeb[1])+intval($tdur[1]))*96+(intval($tdeb[0])+intval($tdur[0]))*96*4; // j'ai ainsi affecté le temps absolu du debut de la note au indices pairs du tableau evts, le temps absolu de la fin de la note au indices impairs; } //tri selon le temps en conservant l'association avec les index asort($evts,SORT_NUMERIC); foreach ($evts as $key => $val) { //constitution de la trame formatée, la correlation avec le tableau de note d'origine est donnée par floor($key/2) qui donne l'index de la note associée au temps $val //j'utilise succesivement les fonctions definie avant pour obtenir le formatage correct des données: //varlen(dechex($val-$timepassed)) pour obtenir un decalage par rapport à l'evenement precedant sous forme VLK, //index pair ou impair pour evenement noteon ou noteoff //findhexnote pour la hauteur de la note en hexa //pour la velocité je n'utilise ici que la velocité-in et 0 pour la velocité off(pas de difference sur mon piano numerique) $hexanotes.=varlen(dechex($val-$timepassed)).(($key%2==0)?"90":"80").findhexnote($mesnotes[floor($key/2)][0]).(($key%2==0)?completechaine(dechex($mesnotes[floor($key/2)][1]),2):"00"); $timepassed=$val; } return $hexanotes; }
//entete typique de fichier midi (MTHD) au format 0 une piste 96beats/quarternote + entete de bloc MTrk //il manque la checksum que je calculerais plus tard $entete="4D546864000000060000000100604D54726B"; $fin="00FF2F00";
//exemple de notes, à récupérer d'un formulaire ou tout ce que vous voudrez $mesnotes=array(); $mesnotes[0]=array("C4",127,"0:0:0","1:0:0"); $mesnotes[1]=array("C5",80,"0:2:0","0:1:0"); $mesnotes[2]=array("G4",80,"0:1:0","0:1:0"); $mesnotes[3]=array("D#4",80,"0:3:0","0:1:0"); $mesnotes[4]=array("G4",127,"1:0:0","1:0:0"); $mesnotes[5]=array("G5",127,"1:0:0","1:0:0");
//obtention de la liste d'evenements noteon et note off $blocnotes=convertnotes($mesnotes);
//calcul de la checksum // à verifier j'ai compté ici la fin de bloc et la checksum elle même mais pas l'entete de track // ca ne pose pas de probleme à mon sequenceur j'assume donc ca comme juste $checksum=completechaine(dechex((strlen($blocnotes)/2+8)),8)
// concatenation des elements du fichiers $myhexdata=$entete.$checksum.$blocnotes.$fin;
//conversion du contenu string hexa en bytes ascii $content=""; for($i=0;$i<strlen($myhexdata);$i+=2){ $content.=chr(hexdec($myhexdata[$i].$myhexdata[$i+1])); }
//ecriture du fichier $fp=fopen("myown.mid",'w'); fwrite($fp,$content); fclose($fp);
?>
|