(Source Wikipédia)
Dans un modéle simple, chaque note de musique se caractérise par :
- sa hauteur (notion de grave / aigu),
- sa durée,
- son intensité (notion de volume sonore),
- son timbre (notion d'instrument).
La hauteur d'une note correspond à sa fréquence fondamentale (en Hz) et on distingue, dans la musique occidentale, douze catégories de hauteurs dont sept principales : do, ré, mi, fa, sol, la et si. Deux hauteurs consécutives de même nom ont des fréquences fondamentales dont l'une est la moitié de l'autre et définissent un intervalle appelé octave.
Pour distinguer les notes de même nom, on numérote les octaves et on accole éventuellement ce numéro au nom de la note. Par exemple le fameux \(\mathbf{la_3}\) et ses 440Hz. Ainsi, si l'on connait la fréquence fondamentale \(f_0\) d'une note donnée, on on déduit la fréquence \(f_n\) d'une autre notre située \(n\) demi tons (les douze catégories) au dessus ar la formule \(f_n = f_0\times 2^{n/12}\)
L'application de la formule précedente permet de calculer les fréquences fondamentales des douze demi tons. Le tableau ci-dessous donne les résultats pour les octaves 0 à 7 et pourra être utile lors de la mise au point de votre programme, pour vérifier les valeurs calculées.
Note / Octave | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|---|
do = si# | 32.70 | 65.41 | 130.81 | 261.63 | 523.25 | 1046.50 | 2093.00 | 4186.01 |
do# = ré b | 34.65 | 69.30 | 138.59 | 277.18 | 554.37 | 1108.73 | 2217.46 | 4434.92 |
ré | 36.71 | 73.42 | 146.83 | 293.66 | 587.33 | 1174.66 | 2349.32 | 4698.64 |
ré# = mi b | 38.39 | 77.78 | 155.56 | 311.13 | 622.25 | 1244.51 | 2489.02 | 4978.03 |
mi = fa b | 41.20 | 82.41 | 164.81 | 329.63 | 659.26 | 1318.51 | 2637.02 | 5274.04 |
fa = mi# | 43.65 | 87.31 | 174.61 | 349.23 | 698.46 | 1396.91 | 2793.83 | 5587.65 |
fa# = solb | 46.25 | 92.50 | 185.00 | 369.99 | 739.99 | 1479.98 | 2959.96 | 5919.91 |
sol | 49.00 | 98.00 | 196.00 | 392.00 | 783.99 | 1567.98 | 3135.96 | 6271.93 |
sol#= la b | 51.91 | 103.83 | 207.65 | 415.30 | 830.61 | 1661.22 | 3322.44 | 6644.88 |
la | 55.00 | 110.00 | 220.00 | 440.00 | 880.00 | 1760.00 | 3520.00 | 7040.00 |
la# = si b | 58.27 | 116.54 | 233.08 | 466.16 | 932.33 | 1864.66 | 3729.31 | 7458.62 |
si = do b | 61.7 | 123.47 | 246.94 | 493.88 | 987.77 | 1975.53 | 3951.07 | 7902.13 |
Sur la portée musicale, les figures des notes indiquent leur durée relativement à une pulsation de référence. Ainsi, comme le montre la figure ci-dessous, la ronde vaut deux blanches. La banche vaut deux noires, noire qui vaut encore deux croches, valant chacune deux double-croches, etc.
La pulsation de référence, ou tempo, est exprimée en nombre de noires par minutes et sera repérée tempo dans vos programmes.
Une note de musique pure n est un signal sinusoïdal de fréquence f, d'amplitude A et de durée d dont l'expression en fonction du temps est la suivante :
\( \forall t \in [0; d],~~~n(t) = A.sin(2\pi ft)\)Dans le monde numérique, un signal (sinusoïdal ou pas) n'est évalué que périodiquement tous les \(T_e\) secondes. On appelle \(T_e\) la période d'échantillonnage, elle est l'inverse de la fréquence d'échantillonnage \(F_e\).
Les CDs audio sont typiquement réalisés en utilisant à \(F_e = 44100~Hz\) et c'est également à cette fréquence d'échantillonnage que les sons que vous synthétiserez seront restitués.
Aucun instrument ne produit toutefois des notes pures. Les dispositifs mécaniques vibratoires mis en œuvre dans les instruments, voix comprise, génèrent des ondes acoustiques complexes, dont le spectre comporte plusieurs fréquences identifiables en plus de la fréquence fondamentale, ainsi que d'autres bruits caractéristiques de l'instrument. Parmi les fréquences identifiables des notes émises par un instrument, on distingue les harmoniques qui sont des multiples ou des sous-multiples de la fréquence fondamentale.
On donne ci-dessous deux allures possibles pour le spectre de la note la3, de fréquence fondamentale 440Hz.
Pour votre implémentation, on limitera les harmoniques à 0.5f, 2f et 3f, d'amplitudes respectives a/4, a/4 et a/8 si a est l'amplitude à la fréquence f.
Note pure f=440Hz (la3) | Note avec harmoniques à 0.5f, 2f, 3f et 4f |
---|---|
La bibliothèque StdAudio.java met à votre disposition un ensemble de méthodes permetant de traiter des signaux audio numériques. Parmi celles-ci, vous aurez plus particulièrement besoin d'utiliser les méthodes suivantes, qui fonctionnent à \(F_e = 44.1~kHz\) :
// envoie un échantillon *in* d'amplitude comprise entre -1 et +1 // sur la sortie audio public static void play(double in) // envoie la suite d'échantillon du tableau *input* // sur la sortie audio (amplitudes entre -1 et +1) public static void play(double[] input)
Exemple d'utilisation
/** * Prend en argument la durée d en secondes * et génère un blanc blanc de durée d, * puis l'envoie sur la sortie audio à l'aide de la méthode StdAudio.play() */ public class TestAudio { public static Random rand = new Random(); public static void main(String[] args) { final double F_ECH = 44100.0 ; double duration = 1.0 ; int i; if (args.length == 1) duration = Double.parseDouble(args[0]); double[] signal = new double[(int)Math.round(duration * F_ECH)]; for (i = 0; i< signal.length; i++) { signal[i] = rand.nextDouble()*2.0 -1.0 ; } for (i = 0; i< signal.length; i++) { StdAudio.play(signal[i]); } } }
Compilation
javac StdAudio javac TestAudio
Exécution : joue un bruit blanc de 1.5 seconde
java TestAudio 1.5
Le fichier TestAudio.java est téléchargeable.
Le projet qui vous est proposé consiste à concevoir un programme capable de jouer des partitions musicales simples. Il pourra en particulier lire les notes qu'il doit jouer dans un fichier texte dont le format est préalablement défini. Les plus déterminés pourront ajouter à la partie audio la visualisation des formes d'onde jouées par l'ordinateur.
Les pages suivantes détaillent les étapes de travail.
La classe Note représente une note de musique par ses attributs
public class Note { final static double[] fondFreq = {32.70, 65.41, 130.81, 261.63, 523.25, 1046.50, 2093.00, 4186.01}; final static String[] tons = {"do", "re", "mi", "fa", "sol", "la", "si"}; final static int[] haut = {0, 2, 4, 5, 7, 9, 11}; public String toneBase ; public char alter ; public int octave ; public double freq ; public double duree; public double amp; double[] signal ; /* * Le constructeur permettant de déclarer/allouer une note par * Note note = new Note(ton, alter, octave, duree, amplitude); */ public Note(String tB, char alt, int oct, double dur, double amp){ duree = dur ; alter = alt ; toneBase = tB ; octave = oct ; freq = freqTone(toneBase, alter, octave) ; // compléter ici la définition de **signal** }}
Pour la mise au point de la classe Note, il est possible d'y ajouter une méthode main() comme celle proposée ci-dessous qui prend le numéro de l'octave en argument de la ligne de commande et joue ses 7 tons de base, à raison d'une seconde chacun. Les paramètres de chaque note sont également affichés dans la console.
/* * méthode main() de test de la classe Note */ public static void main(String[] args){ int i, oct ; if (args.length < 1) oct = 3 ; else oct = Integer.parseInt(args[0]) ; for (i = 0; i< 7; i++){ Note not = new Note(tons[i], ' ', oct, 1.0, 1.0); System.out.print(not.toneBase + ", octave " + not.octave + " f0 =" + fondFreq[not.octave] + "Hz, F ="); System.out.format("%.2f Hz%n",not.freq); not.play(); } }
Dans le fichier Note.java,
private static double freqTone(String toneBase, char alter, int octave)
public void play()
public static Note sToNote(String tonalite, double amplitude, double duree, boolean harmon);
public static double faceToDuration(String figure, int tempo)
Note note = new Note(oldNote) ;Code du constructeur de copie, à insérer dans le code de la classe Note
public Note(Note oldNote){ duree = oldNote.duree ; alter = oldNote.alter ; toneBase = oldNote.toneBase ; octave = oldNote.octave ; amp = oldNote.amp ; freq = oldNote.freq ; signal = oldNote.signal.clone() ; }
Pour jouer des accords, il faut pouvoir jouer plusieurs notes en même temps. Il suffit pour cela de sommer les signaux des différentes notes composant l'accord.
On fournit le squelette suivant pour la classe Accord :
public class Accord { Note[] notes ; double duree ; double[] signal ; public Accord(Note note1) { int i ; notes = new Note[4] ; notes[0] = new Note(note1) ; notes[1] = null ; notes[2] = null ; notes[3] = null ; duree = note1.duree; signal = note1.signal.clone() ; } }
Dans le squelette fourni en page précédente, on peut voir :
- qu'un accord comprend au maximum 4 notes,
- comment déclarer un accord en y affectant un première note
Accord chord = new Accord(note1) ;
public void addNote(Note not)
public void play()
java Accord
La classe Synthe a pour rôle la lecture d'un fichier formaté contenant sur chaque ligne la description d'un accord à jouer. Chaque accord peut ne comporter qu'une seule note. Le format d'une ligne suit les exemples suivants :
re3,croche,0.5 mi3#,la3,noire,0.125
- l'accord ne comprend qu'une note : le ré3. Sa durée est celle d'une croche et son amplitude de 0.5 (amplitude max. = 1.0)
- l'accord comprend 2 notes : le mi3# et le la3. Sa durée est celle d'une noire et son amplitude 0.125
Les durées réelles, en secondes, sont calculées grâce à la méthode Note.faceToDuration() déjà développée précédemment.
La méthode main() de la classe Synthe prend 2 ou 3 arguments sur la ligne de commande :
java Synthe <fichier> <tempo> [harm]|[guitar]Les arguments harm et guitar sont mutuellement exclusifs et optionnels.
Elle lit ensuite le fichier ligne par ligne. Chaque ligne lue est passée en paramètre de type String à la méthode playLigne(), qui se charge de la décoder, créer l'accord correspondant et l'envoyer vers la sortie audio. Un exemple de fichier partition est téléchargeable.
Le code la méthode main() est fourni en page suivante.
public static void playLigne(String ligne, boolean harm, boolean guitar)
Le squelette de la classe Synthe
public class Synthe { static int tempo ; static boolean harm = false ; static boolean guitar = false ; public static void main(String[] args) { if (args.length < 2) { System.out.println("Usage : java Synthe <fichier> <tempo> <harm/guitar>"); System.exit(-1); } tempo = Integer.parseInt(args[1]); if (args.length == 3){ if (args[2].equals("harm")) harm = true ; else if (args[2].equals("guitar")) guitar = true ;} try { BufferedReader fichier = new BufferedReader(new FileReader(args[0])); String ligne; while ((ligne = fichier.readLine()) != null) { playLigne(ligne, harm, guitar);} fichier.close(); } catch (IOException ex) { ex.printStackTrace(); } System.exit(0); }}
- Concevoir une méthode permettant de tracer l'évolution temporelle du signal envoyé à la sortie audio. Elle utilisera pour cela la bibliothèque StdDraw déjà utilisée en TP. Il peut s'avérer intéressant de modifier quelque peu les autres méthodes de la classe, ainsi que les variables de classe.
- Reconnaître le morceau du fichier partitionTest ;-)
Les recommandations suivantes sont à suivre impérativement. Leur respect représentera une part non négligeable des points attribués à vos travaux.
Ces recommandations sont susceptaible de s'enrichir au fur et à mesure des semaines, pensez à la consulter fréquemment.
Bon courage à tous !
Space | Forward |
---|---|
Left, Down, Page Down | Next slide |
Right, Up, Page Up | Previous slide |
P | Open presenter console |
H | Toggle this help |