Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

avertissement

imports

nature du son

comme vous le savez sans doute, lorsqu’on enregistre un morceau de musique, on capture la position de la membrane du microphone au cours du temps

puisqu’il s’agit de son, la membrane oscille autour de sa position d’équilibre, dans un mouvement pseudo-périodique, et la fréquence à un moment donné détermine la hauteur du son qu’on entend

ainsi la fréquence de 440Hz a été définie comme étant la fréquence du LA (enfin pour être précis, d’un LA, on y reviendra)

comment on capture du son

une technique pour enregistrer le son consiste à simplement capturer la position de la membrane à intervalles réguliers : on appelle cela l’échantillonnage, qui produit en sortie une collection de valeurs numériques

les fréquences audibles sont comprises, disons, pour être très large, entre 20 Hz et 20 kHz
du coup pour ne pas perdre en précision, on échantillonne traditionnellement à une fréquence de 44.1 kHz (chiffre qui date de l’époque des CD)

ce qui signifie que si on produit un tableau de 44100 valeurs qui représentent une sinusoïde parfaite, on pourra jouer cela comme un son de 1s et sur une note continue; ce sera notre premier exercice

synthétiseur - fréquence

reste à déterminer l’amplitude, pour l’instant on prend une amplitude de 1

imaginons que nous voulions produire un son correspondant à un LA à 440 Hz, sur une seconde:

  1. nous devons donc calculer un tableau qui fait combien d’entrées ?

  2. quelle est en fonction du temps, et donc sur l’intervalle [0,1][0, 1],
    l’équation de la fonction qui nous intéresse ?

  3. comment on peut s’y prendre pour calculer ce tableau ?



Loading...

commodité

comme on ne va produire que des sons échantillonnés à 44.100 Hz, ce sera plus commode de ne pas avoir à le répéter à chaque fois

Loading...

on en fait une fonction

pour généraliser un petit peu, on va écrire une fonction qui

pour les rapides

on veut obtenir un effet de ‘note qui monte’

améliorer un peu pour générer une courbe avec un fréquence qui croit (ou décroit) linéairement avec le temps

écrire une fonction sine_linear(freq1, freq2, duration)

réglage du volume

crescendo

imaginons qu’on veuille produire un son de plus en plus fort, par exemple qui monte crescendo de manière linéaire sur toute la durée du son

  1. comment on pourrait faire ça ?

  1. en faire une fonction

    def crescendo_sine(freq, duration):
         ...
  1. ajouter un paramètre pour pouvoir décroître

    def crescendo_sine(freq, duration, increase=True):
         ...
  1. avancés: est-ce qu’on ne pourrait pas faire un choix un peu plus malin ?

Ellipsis

concaténation

on sait maintenant produire des notes élémentaires

sachant que la note DO immédiatement au dessus du la-440 a une fréquence de l’ordre de 523 Hz, comment pourrait-on maintenant produire une succession de deux notes la et do ?

amplitude et types

jusqu’ici, chaque échantillon est représenté par un nombre flottant entre -1 et 1

il se trouve que ça n’est pas forcément le plus pertinent comme approche, notamment lorsqu’il va s’agir de sauver notre son sur fichier

aussi nous allons maintenant nous poser la question de changer d’échelle - et de type de données - pour utiliser plutôt des entiers 16 bits (que pour rappel on a à notre disposition avec numpy.int16)

entiers signés ou non

ce qui nous amène à une petite digression: profitons-en pour regarder un peu comment sont encodés les entiers;
on regarde ici les types int8 et uint8 car c’est plus simple, le principe est exactement le même pour des tailles plus grandes

il y a deux types d’encodages pour les entiers:

du coup avec le type int16 on va pouvoir encoder l’intervalle [-32768, 32767]

32768

ça veut dire que si on sort de cet intervalle on va avoir des surprises

mise à l’échelle

exercice

en vous souvenant qu’on a à notre disposition la méthode array.astype() pour fabriquer une copie d’un tableau numpy convertie dans un autre type, écrivez une fonction qui transforme notre tableau de flottants dans [-1, 1] en un tableau d’entiers signés 16bits

et pour préserver le niveau sonore, il faut que les entrés maximales - i.e. 1 ou -1 dans le 1er format - correspondent au maximum codable dans le second format

le son produit doit être totalement identique - le volume notamment

fréquences des notes de la gamme

dans cette partie, nous allons calculer les fréquences des notes de la gamme

intervalles

notre oreille reconnait bien les intervalles entre deux notes
par exemple si vous jouez les deux extraits ci-dessous, vous allez reconnaitre dans les deux cas le pin-pon des pompiers

Loading...
Loading...

notre oreille identifie la même mélodie, mais à des hauteurs différentes
ici les deux notes utilisées (la - si pour le 1er, fa - sol pour le 2nd), sont dans les deux cas séparées de 2 “crans” dans la gamme chromatique
(on dit que les deux notes constituent un intervalle de 2 demi-tons, soit un ton)
c’est parce que c’est le même intervalle que notre oreille entend dans les deux cas la même mélodie

un intervalle = un rapport entre fréquences

enfin, il faut savoir que ce qui caractérise un intervalle, c’est le rapport entre les fréquences des deux notes

ainsi par exemple, vous pouvez constater que si on multiplie une fréquence par 2, on entend une note qui ressemble beaucoup à la premiére
il se trouve que le fait de multiplier la fréquence par 2 permet d’obtenir une note une octave au dessus (c’est-à-dire de passer d’un DO au DO au dessus)

calculons les fréquences des notes

à ce stade on a toutes les informations pour calculer les fréquences des notes de la gamme (dite bien tempérée)

en effet on sait que, puisque c’est toujours le même intervalle, un demi-ton correspond à un rapport constant - qu’on va appeler α\alpha entre (les fréquences de) deux notes successives de la gamme

do♯do=redo♯=…sila♯=do′si=α\frac{do\sharp}{do} = \frac{re}{do\sharp} = \ldots \frac{si}{la\sharp} = \frac{do'}{si} = \alpha

et comme par ailleurs on sait qu’entre les deux do il y a une octave  donc do′do=2 \frac{do'}{do} = 2

mais c’est aussi do′do=do′si.sila♯.la♯la...redo♯.do♯do=α12 \frac{do'}{do} = \frac{do'}{si}.\frac{si}{la\sharp}.\frac{la\sharp}{la}...\frac{re}{do\sharp}.\frac{do\sharp}{do} = \alpha^{12}

d’où il ressort que α12=2\alpha^{12} = 2


exercice

  1. calculer - sans boucle for - un tableau contenant les 13 - de do à do’ inclus - rapports entre do et les notes de la gamme
    (ratios[0] devrait valoir 1, et ratios[12] devrait valoir 2)

0120do121221/12do♯2(212)222/12re...11(212)11211/12la♯122212/12do′\begin{array}{cccc} 0 & 1 & 2^0 & do\\ 1 & \sqrt[^{12}]{2} & 2^{1/12} & do\sharp\\ 2 & (\sqrt[^{12}]{2})^2 & 2^{2/12} & re\\ ...\\ 11 & (\sqrt[^{12}]{2})^{11} & 2^{11/12} & la\sharp\\ 12 & 2 & 2^{12/12} & do'\\ \end{array}
  1. on a besoin d’une fonction qui calcule la fréquence d’une note à partir de son nom,
    et on veut bien sûr que la→440la \rightarrow 440

  1. on veut vérifier notre code; pour ça on pourrait écrire ceci qui devrait retourner True

False

rationnels approchants - visuel

pour comprendre les harmonies, ce qui intéressant c’est que parmi les ratios qu’on a calculés plus haut, certains sont très proches de rapports rationnels simples

np.float64(1.2599210498948734)
np.float64(1.498307076876682)

on visualise ça: les différentes puissances de α\alpha, en superposant les rationnels 32\frac{3}{2}, 54\frac{5}{4} et 43\frac{4}{3}

<Figure size 200x600 with 1 Axes>

les accords: superposer plusieurs sons

on veut jouer des accords, c’est à dire plusieurs notes en même temps; comment faire ?

sauver un son dans un .wav

on peut facilement sauver nos sons grâce à la librairie scipy
par contre il faut savoir que le format le plus robuste est celui qui utilise les entiers 16 bits qu’on a vus plus haut

exercice

  1. chercher dans la documentation comment sauver un son dans un fichier .wav

  1. sauver un de vos morceaux (par exemple la_do)

  1. relisez-le

  1. assurez-vous que le résultat est conforme au morceau de départ

un vrai son

on part d’un petit fichier media/sounds-cello.wav

Loading...

exercice

  1. lire le fichier (ranger le signal dans une variable data) (voyez wavfile.read)

  1. écoutez le

  1. afficher le samplerate utilisé dans le fichier

  1. afficher le nombre d’échantillons

  1. afficher la longueur du morceau en secondes

à quoi ça ressemble

exercice affichez la position de la membrane en fonction du temps à l’aide de la fonction plt.plot()

effet d’echo

maintenant on veut ajouter un effet d’echo; il nous faut pour cela

sauf que si on s’y prend comme cela:

c’est ce qu’on essaie d’illustrer ici

exercice v1

  1. traduire delay en nombre d’échantillons offset

  2. produire le son avec echo, sur une durée correspondant au son de départ (on jette l’echo après cette durée là)

  3. observez le signal résultat, en l’affichant avec plt.plot()

exercice v2

  1. idem mais cette fois on produit une durée un peu plus longue, correspondant à la somme de la durée de départ et du retard

  2. écoutez le résultat

  3. observez le signal

transposer

transposer d’une octave

on a vu qu’une octave correspond à une fréquence deux fois plus élevée

partant de par exemple data, comment produire un son une octave au dessus ?
(on s’astreint à ne pas modifier le samplerate)







je vous laisse y réfléchir un moment...







pour élever d’une octave, il suffit d’ignorer un échantillon sur deux

pourquoi ? de cette façon on va artificiellement

exercice

fabriquer un son qui soit similaire à celui dans data, mais une octave au dessus

naturellement le profil reste le même mais l’échelle des X est plus courte (deux fois moins d’échantillons)

transposer d’une quinte

pour transposer d’une quinte, il nous faut multiplier la fréquence par 3/2; on peut utiliser une approche voisine

sauf que cette fois, il faut un peu interpoler; on est donc amené à faire des moyennes comme ceci

data         data3  
0    0       0
1    1+2/2   1
2    --
3    3       2
4    4+5/2   3
5    --
...

exercice appliquez l’idée ci-dessus :

  1. créez un tableau data3 dont la taille est 2/3 de celle de data

  2. remplir dans data3 les données de rang pair
    qui correspondent aux multiples de 3 dans le tableau de départ

  3. remplir dans data3 les données de rang impair
    en implémentant l’interpolation

remarque: nos data sont en int16, on va s’efforcer de continuer à travailler dans ce format

la fraction la plus proche (avancés - sans exercice)

on peut s’amuser à calculer, pour chaque note, la fraction la plus proche - si on se restreint à des rationnels avec un dénominateur “petit”

pour ça on se fixe par exemple N=7 et pour chaque note x, on veut minimiser abs(x-r) pour r étant dans l’espace

r∈{1+p/q,q<=N,0<=p<=q}r\in\{1 + p/q, q<=N, 0<=p<=q\}

si on voulait faire ça en Python pur, on pourrait écrire quelque chose comme

{Fraction(1, 1), Fraction(8, 7), Fraction(7, 6), Fraction(6, 5), Fraction(5, 4), Fraction(9, 7), Fraction(4, 3), Fraction(7, 5), Fraction(10, 7), Fraction(3, 2), Fraction(11, 7), Fraction(8, 5), Fraction(5, 3), Fraction(12, 7), Fraction(7, 4), Fraction(9, 5), Fraction(11, 6), Fraction(13, 7), Fraction(2, 1)}
0.001128615415545357
(Fraction(3, 2), np.float64(0.0011298906275254623))
(Fraction(3, 2), np.float64(0.0011298906275254623))

les accords harmonieux

si on ne garde que les notes qui sont très proches - avec une erreur relative de moins de 0.5%
on trouve les intervalles do-fa et do-sol