diff --git a/cours/audio/Makefile b/cours/audio/Makefile new file mode 100644 index 0000000..7d29470 --- /dev/null +++ b/cours/audio/Makefile @@ -0,0 +1,44 @@ +HEPTC=heptc +HEPTLIB=$(shell heptc -where) +CC=gcc +CFLAGS=-g $(shell pkg-config --cflags --libs sdl2 sndfile) -I$(HEPTLIB)/c -lm +PYGMENTS=python -m pygments -x + +TARGET=audio +SOURCES=audio_c/audio_types.c \ + audio_c/audio.c \ + buffer.c \ + vcd_lib.c \ + mathext.c \ + vcd.c \ + main.c + +.PHONY: all clean test + +all: $(TARGET) audio.html main.html + +%.html: %.ept + $(PYGMENTS) -l ../../../notes/heptagon.py:HeptagonLexer -O full -o $@ $^ + +%.html: %.c + $(PYGMENTS) -O full -o $@ $^ + +clean: + rm -f $(TARGET) *.{epci,log,mls,obc,html} $(TARGET).{pdf,tex} + rm -rf audio_c + +test: $(TARGET) + ./$(TARGET) + +$(TARGET): $(SOURCES) + @pkg-config --exists sdl2 || \ + ( echo "La bibliothèque SDL2 est absente."; exit 1 ) + @pkg-config --exists sndfile || \ + ( echo "La bibliothèque sndfile est absente."; exit 1 ) + $(CC) $(CFLAGS) -o $@ -I audio_c -I. $^ + +audio_c/audio_types.c audio_c/audio.c: audio.ept main.c mathext.epci vcd.epci + $(HEPTC) -target c $< + +%.epci: %.epi + $(HEPTC) $< diff --git a/cours/audio/README.md b/cours/audio/README.md new file mode 100644 index 0000000..335e71b --- /dev/null +++ b/cours/audio/README.md @@ -0,0 +1,14 @@ +# Synthèse sonore élémentaire en Heptagon + +Ce dossier contient quelques noeuds Heptagon élementaires qui produisent du son. + +Il utilise les bibliothèques SDL2 et sndfile. Elles doivent être installées via +le gestionnaire de paquet de votre système d'exploitation (`apt-get` sous +GNU/Linux Debian ou Ubuntu, `pacman` sous Arch Linux, `brew` sous macOS, etc.). + +```shell +$ make +$ ./audio +``` + +Pour essayer différents codes, il faut éditer le noeud `main` dans `audio.ept`. diff --git a/cours/audio/audio.ept b/cours/audio/audio.ept new file mode 100644 index 0000000..f273bd6 --- /dev/null +++ b/cours/audio/audio.ept @@ -0,0 +1,515 @@ +(******************************************************************************) +(* SYNTHÈSE SONORE ÉLÉMENTAIRE EN HEPTAGON *) +(******************************************************************************) + +(* Le but de ce fichier est de démontrer quelques techniques élémentaires de + génération de son en Heptagon, à travers de modestes expérimentations. Il n'a + bien sûr pas vocation à se substituer à un cours de traitement du signal ou + d'acoustique. En revanche, il peut facilement servir d'illustration de + diverses techniques de programmation en Heptagon. *) + +(* Le flot d'échantillons sonores produits par ce programme synchrone est + branché à un petit bout de code C qui les envoie au système sonore de votre + système d'exploitation par le biais de la bibliothèque SDL2. Votre OS les + transmet à la carte son qui elle même les envoie à vos enceintes, casque ou + écouteurs. *) + +(* Les langages synchrones ont été utilisés pour la synthèse sonore. Si ce sujet + vous intéresse, vous pouvez par exemple consulter la page du langage Faust, à + la syntaxe rudimentaire mais aux bibliothèques acoustiques, sonores et + musicales très développées : http://faust.grame.fr *) + +(* On va utiliser une petite bibliothèque de composants mathématiques. Les + curieuses et curieux pourront aller voir mathext.epci. *) + +open Mathext + +(* Avant de commencer, on a besoin de quelques définitions et outils. *) + +(* Le nombre d'échantillons, c'est à dire ici de pas synchrones, que le système + sonore va consommer une seconde. *) + +const period : int = 44100 + +(* Un signal mono est un simple flot de nombres à virgule flottante. *) + +type mono = float + +(* Un signal stéréo fournit deux échantillons, gauche et droit, la carte son se + chargeant de les mixer pour donner l'impression d'un son 'surround'. *) + +type stereo = { l : float; r : float } + +(* Le signal constant silencieux. *) + +const silence : stereo = { l = 0.0; r = 0.0 } + +(* On peut dupliquer un signal mono pour obtenir un signal stéréo + inintéressant, les deux canaux portant la même valeur. *) + +fun stereo_of_mono(a : mono) returns (o : stereo) +let + o = { l = a; r = a } +tel + +(* On peut appliquer un gain à un signal stéréo, c'est à dire le multiplier par + un flottant pour l'amener à une amplitude différente. *) + +fun stereo_gain(g : float; s : stereo) returns (o : stereo) +let + o = { l = g *. s.l; r = g *. s.r }; +tel + +(* Étant donné deux signaux, on peut les combiner via leur somme. *) + +fun stereo_sum(s1, s2 : stereo) returns (o : stereo) +let + o = { l = s1.l +. s2.l; r = s1.r +. s2.r } +tel + +(* Quand on utilise les deux fonctions qu'on vient de définir, gare à + l'amplitude en sortie ! Une amplitude trop élevée risque de dépasser la + capacité de votre carte son, enceintes ou écouteurs, ce qui cause un + phénomène de saturation : tous les échantillons d'amplitude trop élevée sont + écrasés sur l'amplitude maximale. *) + +(* La fonction mix ci-dessous pallie le défaut de la fonction stereo_sum en + renormalisant le résultat. De plus, elle traite un tableau de signaux, et + donc moralement un nombre d'entrées arbitraires. *) + +fun stereo_mix<>(s : stereo^n) returns (o : stereo) +let + o = stereo_gain(1.0 /. float(n), fold<> stereo_sum(s, silence)); +tel + +(* On peut commencer à écouter un peu de son, par exemple celui du silence. *) + +node main0() returns (o : stereo) +let + o = silence; +tel + +(* Quid du noeud suivant ? *) + +node cracks() returns (o : stereo) +let + o = { l = 4200.0; r = 4200.0 }; +tel + +(* On entendu un craquement, puis plus rien, puis un craquement lorsqu'on + interromp le programme. Pourquoi ? + + Physiquement, le son est une vibration produit par une onde acoustique, c'est + à dire une oscillation de la pression de l'air. Autrement dit, il s'agit + d'une *variation*. Donc, le signal constant ne peut pas donner lieu à un son, + sauf au premier instant (passage de 0 à 4200) puis lorsqu'on interromp le + programme (passage de 4200 à 0). + + Et si on essayait un signal qui varie ? Par exemple, un signal carré qui + passe de 1 à 0 toutes les demi-secondes. *) + +node periodic(p : int) returns (o : int) +var n : int; +let + o = 0 fby (if n = p then 0 else n); + n = o + 1; +tel + +node beats_1() returns (o : stereo) +let + o = stereo_of_mono(if periodic(period) <= period / 2 then 1.0 else -. 1.0); +tel + +(* On obtient une série de battements simples. Faire en sorte que le canal droit + soit l'opposé du canal gauche produit un effet intéressant. *) + +node beats_2() returns (o : stereo) +var l : float; +let + l = if periodic(period) <= period / 2 then 1.0 else -. 1.0; + o = { l = l; r = -. l }; +tel + +(* Essayons maintenant de générer un signal qui croît indéfiniment. *) + +node fcnt(ini : float; step : float) returns (o : float) +let + o = ini fby (o +. step); +tel + +node sawtooth_1() returns (o : stereo) +let + o = stereo_of_mono(fcnt(0.0, 1.0)); +tel + +(* On entend quelques craquements, puis plus rien. Normal : ce signal n'oscille + pas vraiment, ou du moins pas avant d'atteindre l'overflow. Pourquoi ne pas + tester un signal périodique en dents de scie, dans ce cas ? *) + +node sawtooth_2() returns (o : stereo) +var t : float; +let + t = float(periodic(128)); + o = stereo_of_mono(t); +tel + +(* Tiens, un son à peu près constant ! Pas très harmonieux cependant. *) + +(* Est-ce qu'appliquer un gain ferait une différence ? Pour bien observer la + différence, on n'a qu'à faire passer le gain de 0 à 1 à chaque seconde. + C'est très facile à programmer en Heptagon. *) + +node sawtooth_3() returns (o : stereo) +var t : float; g : float; +let + t = float(periodic(128)); + g = float(periodic(period)) /. float(period); + o = stereo_gain(g, stereo_of_mono(t)); +tel + +(* On entend nettement le signal en dent de scie, avec un pic à la fin de la + seconde. De façon intéressante, si on augmente le gain, le son apparaît comme + plus pincé, un peu comme les notes d'une guitare. *) + +node sawtooth_4() returns (o : stereo) +var t : float; g : float; +let + t = float(periodic(128)); + g = 3.0 *. float(periodic(period)) /. float(period); + o = stereo_gain(g, stereo_of_mono(t)); +tel + +(* En augmentant la période, les pics s'éloignent, en la diminuant, les + pics se rapprochent. *) + +node period_per_sec(a : int) returns (o : float) +let + o = float(periodic(period / a)) /. float(period / a); +tel + +node sawtooth_5() returns (o : stereo) +var t : float; g : float; +let + t = float(periodic(128)); + g = period_per_sec(2); + o = stereo_gain(g, stereo_of_mono(t)); +tel + +(* On peut aussi appliquer des gains différents sur le canal mono et stéréo. *) + +node every_sec(s : int) returns (c : bool) +let + c = periodic(period * s) = ((- 1) fby 0); +tel + +node sawtooth_6() returns (o : stereo) +var t : float; g1, g2 : float; +let + t = float(periodic(128)); + o = { l = g1 *. t; r = g2 *. t }; + automaton + state FastLeftSlowRight + do g1 = period_per_sec(1); + g2 = period_per_sec(8); + until every_sec(5) then SlowLeftFastRight + + state SlowLeftFastRight + do g1 = period_per_sec(5); + g2 = period_per_sec(1); + until every_sec(5) then FastLeftSlowRight + end +tel + +(* Tous ces sons ne sont pas très harmonieux. Peut-on en obtenir de plus purs ? + + Le traitement du signal nous enseigne, via la théorie de la transformée de + Fourier, que tout signal raisonnablement régulier peut se décomposer en une + somme (infinie) de sinusoïde. Autrement dit, les signaux sinusoïdaux peuvent + servir de briques de base élémentaires mais universelles. Considérés comme + des signaux audio, ils forment des tons purs, élémentaires. + *) + +node pure_tone(p : float) returns (o : float) +var t : float; +let + t = fcnt(0.0, 1.0); + o = sin(t *. (p /. float(period)) *. 2.0 *. Mathext.pi); +tel + +(* Par exemple, la sinusoïde de fréquence 440.1 Hz, communément désignée sous le + nom de La 440, devrait vous être familière. *) + +node main_pure_1() returns (o : stereo) +let + o = stereo_of_mono(pure_tone(440.0)); +tel + +(* En plus d'être la tonalité du téléphone, elle sert de référence pour + l'accordage des pianos, violons et d'autres instruments. + + https://fr.wikipedia.org/wiki/La_440 *) + +(* En mélangeant plusieurs sinusoïdes ensembles, on peut obtenir des effets + rétro assez amusants. *) + +node some_pure_tone(p : float; i : int) returns (s : stereo) +let + s = stereo_gain(period_per_sec(i + 1), stereo_of_mono(pure_tone(p))); +tel + +node oscillating_counter<>(i : int) returns (last o : int = 0) +var step : int; +let + step = if every_sec(1) then 1 else 0; + automaton + state Init + do o = i + until true then Increase + + state Increase + do o = last o + step + until o >= m then Decrease + + state Decrease + do o = last o - step + until o <= 0 then Increase + end +tel + +node main_pure_2() returns (o : stereo) +var periods : float^3; speeds : int^3; +let + periods = [440.0, 261.6256, 4186.009]; + speeds = map<<3>>(oscillating_counter<<10>>)([1, 3, 7]); + o = stereo_mix<<3>>(map<<3>> some_pure_tone(periods, speeds)); +tel + +(* Enfin, les amatrices et amateurs de piano pourront trouver sur la page + + https://en.wikipedia.org/wiki/Piano_key_frequencies + + une formule associant une fréquence de sinusoïde à une note de piano. On peut + l'utiliser comme suit. *) + +fun piano_freq_of_key(k : int) returns (f : float) +let + f = Mathext.pow(2.0, (Mathext.float(k) -. 49.0) /. 12.0) *. 440.0; +tel + +node tone_of_piano_key(k : int) returns (o : stereo) +let + o = stereo_of_mono(pure_tone(piano_freq_of_key(k))); +tel + +node maintain(c : bool; x : int on c; ini : int) returns (o : int) +let + o = merge c x ((ini fby o) whenot c); +tel + +node main_pure_3() returns (o : stereo) +var k : int; c : bool; +let + o = tone_of_piano_key(k); + k = maintain(c, 40 + periodic(53 - 40), 40); + c = periodic(period) = 0; +tel + +(* On peut essayer de programmer un piano midi. *) + +(* Pour générer des transitions propres entre les notes, on a besoin de modifier + nos tons à travers une "enveloppe". La plus classique est l'enveloppe dite + "Attack-Decay-Sustain-Release", cf. Wikipédia. + + https://en.wikipedia.org/wiki/Envelope_(music)#ADSR + + Le noeud ci-dessous produit une telle enveloppe périodiquement, tous les t + instants. L'enveloppe prend la forme d'un gain entre 0 et 1. + + Les paramètres a, d et s doivent-être tels que 0.0 < a + d + s < 1.0. Ils + expriment la fraction de t correspondant à chacune des quatre phases, la + phase d étant la fraction de t définie comme 1 - a - d - s. + + Le paramètre s_level est le niveau de la phase S, entre 0 et 1 donc. + + *) + +node adsr_envelope(t : int; a, d, s : float; s_level : float) + returns (e : float) +var c, a_stop, d_stop, s_stop : int; +let + a_stop = int(float(t) *. a); + d_stop = a_stop + int(float(t) *. d); + s_stop = d_stop + int(float(t) *. s); + c = periodic(t); + automaton + state Attack + do e = float(c) /. float(a_stop); + unless c >= a_stop continue Decay + + state Decay + var f : float; + do e = 1.0 -. (1.0 -. s_level) *. f; + f = float(c - a_stop) /. float(d_stop - a_stop); + unless c >= d_stop continue Sustain + + state Sustain + do e = s_level; + unless c >= s_stop continue Release + + state Release + do e = s_level *. (1.0 -. float(c - s_stop) /. float(t - s_stop)); + until c + 1 >= t continue Attack + end +tel + +node midi_piano<>(keys : int^2^n; time : int^n) returns (o : stereo) +var i, j : int; next : bool; duree_mesure : int; e : float; +let + duree_mesure = 2 * period; (* 1 mesure = 8 noires = 4 sec à 120 BPM. *) + + i = periodic(n); + j = maintain(next, i, 0); + o = stereo_gain(e, stereo_mix<<2>>(map<<2>> tone_of_piano_key(keys[>j<]))); + e = adsr_envelope(duree_mesure / time[> j <], 0.3, 0.1, 0.4, 0.5); + + automaton + state Next + do next = true + until true then Wait + + state Wait + var c : int; + do next = false; + c = 0 fby (c + 1); + until c >= (duree_mesure / time[> j <]) then Next + end +tel + +const num_keys : int = 82 + +node main_pure_4() returns (o : stereo) +var keys : int^2^num_keys; time : int^num_keys; +let + keys = [ + [44, 00], [37, 00], [40, 00], [42, 00], + [44, 00], [37, 00], [40, 00], [42, 00], + [44, 00], [37, 00], [40, 00], [42, 00], + [44, 00], [37, 00], [40, 00], [42, 00], + [44, 00], [37, 00], [41, 00], [42, 00], + [44, 00], [37, 00], [41, 00], [42, 00], + [44, 00], [37, 00], [41, 00], [42, 00], + [44, 00], [37, 00], [41, 00], [42, 00], + + [44, 00], + [37, 00], + [40, 37], [42, 00], [44, 00], + [37, 00], [40, 00], [42, 00], + + [35, 39], [32, 00], [35, 00], [37, 00], + [39, 00], [32, 00], [35, 00], [37, 00], + [35, 39], [32, 00], [35, 00], [37, 00], + [39, 00], [32, 00], [35, 00], + + [42, 00], + [35, 00], + [35, 40], [39, 00], [42, 00], + [35, 00], [40, 00], [39, 00], + [33, 37], [30, 00], [33, 00], [35, 00], + [33, 00], [30, 00], [33, 00], [35, 00], + [33, 37], [30, 00], [33, 00], [35, 00], + [37, 00], [30, 00], [33, 00], + + [0, 0], [0, 0], [0, 0], [0, 0] (* silence *) + ]; + time = [ + 4, 4, 8, 8, + 4, 4, 8, 8, + 4, 4, 8, 8, + 4, 4, 8, 8, + 4, 4, 8, 8, + 4, 4, 8, 8, + 4, 4, 8, 8, + 4, 4, 8, 8, + + 2, + 2, + 8, 8, 2, + 2, 8, 8, + 4, 4, 8, 8, + 4, 4, 8, 8, + 4, 4, 8, 8, + 4, 4, 4, + + 2, + 2, + 8, 8, 2, + 2, 8, 8, + 4, 4, 8, 8, + 4, 4, 8, 8, + 4, 4, 8, 8, + 4, 4, 4, + + 1, 1, 1, 1 (* silence *) + ]; + o = midi_piano<>(keys, time); +tel + +(* Bonus : la méthode de Karplus-Strong pour la synthèse de son de guitare. + + https://en.wikipedia.org/wiki/Karplus%E2%80%93Strong_string_synthesis + + http://sites.music.columbia.edu/cmc/MusicAndComputers/chapter4/04_09.php +*) + +node flip(i: int) returns (o: float) +let + o = if (i % 2 = 0) then 1.0 else -.1.0 +tel + +node karplus_strong<>() returns (y : float) +var b : float^l; i: int; +let + i = 0 fby ((i+1) % l); + y = 0.5 *. (b[>i<] +. 0.0 fby y); + b = (mapi<> flip ()) fby ([b with [i] = y]); +tel + +node repeat<>(x : stereo) returns (o : stereo) +var last t : stereo^n = silence^n; +let + automaton + state Fill + do o = x; + t = [ last t with [ periodic(n) ] = x ] + until periodic(n) = n - 1 then Repeat + + state Repeat + do o = t[> periodic(n) <] + end +tel + +node saturating_counter(max : int) returns (o : int) +var c : int; +let + c = 0 fby (c + 1); + o = if c < max then c else max; +tel + +node main_kp() returns (o : stereo) +var s : stereo; +let + s = repeat<>({ l = karplus_strong<<115>>(); + r = karplus_strong<<55>>() }); + o = stereo_gain(float(saturating_counter(5 * period)) /. float(5 * period), + s); +tel + +(* Le noeud principal du programme. *) + +(* Vous pouvez choisir un des noeuds principaux main_XXX écrits ci-dessus, ou + bien écrire le votre. *) + +node main() returns (o : stereo) +let + o = main_pure_4(); +tel diff --git a/cours/audio/buffer.c b/cours/audio/buffer.c new file mode 100644 index 0000000..20cfffa --- /dev/null +++ b/cours/audio/buffer.c @@ -0,0 +1,61 @@ +#include "buffer.h" + +#include +#include +#include +#include +#include + +#define max(a, b) ((a) <= (b) ? (a) : (b)) + +void *malloc_checked(size_t size) { + void *result = malloc(size); + if (!result) { + perror("malloc()"); + exit(EXIT_FAILURE); + } + return result; +} + +char *strdup_checked(const char *s) { + char *result = strdup(s); + if (!result) { + perror("strdup()"); + exit(EXIT_FAILURE); + } + return result; +} + +buffer_t *buffer_alloc(size_t initial_size) { + buffer_t *buff = malloc_checked(sizeof *buff); + buff->data = malloc_checked(initial_size * sizeof *buff->data); + buff->size = initial_size; + buff->occupancy = 0; + return buff; +} + +void buffer_free(buffer_t *buffer) { + assert (buffer); + + free(buffer->data); + free(buffer); +} + +void buffer_resize(buffer_t *buff, size_t new_size) { + assert (buff); + assert (new_size >= buff->size); + + unsigned char *new_data = malloc_checked(new_size); + memcpy(new_data, buff->data, buff->occupancy); + free(buff->data); + buff->data = new_data; + buff->size = new_size; +} + +void buffer_write(buffer_t *buff, void *data, size_t data_size) { + assert (buff); + if (buff->occupancy + data_size > buff->size) + buffer_resize(buff, max(buff->size + data_size, 2 * buff->size)); + memcpy(buff->data + buff->occupancy, data, data_size); + buff->occupancy += data_size; +} diff --git a/cours/audio/buffer.h b/cours/audio/buffer.h new file mode 100644 index 0000000..f8a2743 --- /dev/null +++ b/cours/audio/buffer.h @@ -0,0 +1,27 @@ +#ifndef BUFFER_H +#define BUFFER_H + +#include + +/* A simple type of append-only buffers. */ + +typedef struct buffer { + unsigned char *data; + size_t size; + size_t occupancy; +} buffer_t; + +void *malloc_checked(size_t size); +char *strdup_checked(const char *); + +buffer_t *buffer_alloc(size_t initial_size); +void buffer_free(buffer_t *buff); + +void buffer_write(buffer_t *buff, void *data, size_t data_size); + +#define buffer_foreach(ty, var, buffer) \ + for (ty *var = (ty *)buffer->data; \ + var < (ty *)(buffer->data + buffer->occupancy); \ + var++) + +#endif /* BUFFER_H */ diff --git a/cours/audio/hept_ffi.h b/cours/audio/hept_ffi.h new file mode 100644 index 0000000..2d9b81b --- /dev/null +++ b/cours/audio/hept_ffi.h @@ -0,0 +1,52 @@ +#ifndef HEPT_FFI_H +#define HEPT_FFI_H + +#define UNPAREN(...) __VA_ARGS__ + +#define DECLARE_HEPT_FUN(module, name, inputs, outputs) \ + typedef struct { outputs; } module ## __ ## name ## _out; \ + void module ## __ ## name ##_step(UNPAREN inputs, \ + module ## __ ## name ## _out *) + +#define DECLARE_HEPT_FUN_NULLARY(module, name, outputs) \ + typedef struct { outputs; } module ## __ ## name ## _out; \ + void module ## __ ## name ##_step(module ## __ ## name ## _out *) + +#define DEFINE_HEPT_FUN(module, name, inputs) \ + void module ## __ ## name ##_step(UNPAREN inputs, \ + module ## __ ## name ## _out *out) + +#define DEFINE_HEPT_FUN_NULLARY(module, name, inputs) \ + void module ## __ ## name ##_step(module ## __ ## name ## _out *out) + +#define DECLARE_HEPT_NODE(module, name, inputs, outputs, state) \ + typedef struct { outputs; } module ## __ ## name ## _out; \ + typedef struct { state; } module ## __ ## name ## _mem; \ + void module ## __ ## name ##_step(UNPAREN inputs, \ + module ## __ ## name ## _out *, \ + module ## __ ## name ## _mem *); \ + void module ## __ ## name ##_reset(module ## __ ## name ## _mem *) + +#define DECLARE_HEPT_NODE_NULLARY(module, name, outputs, state) \ + typedef struct { outputs; } module ## __ ## name ## _out; \ + typedef struct { state; } module ## __ ## name ## _mem; \ + void module ## __ ## name ##_step(module ## __ ## name ## _out *, \ + module ## __ ## name ## _mem *); \ + void module ## __ ## name ##_reset(module ## __ ## name ## _mem *) + +#define DEFINE_HEPT_NODE_RESET(module, name) \ + void module ## __ ## name ##_reset(module ## __ ## name ## _mem *mem) + +#define DEFINE_HEPT_NODE_STEP(module, name, inputs) \ + void module ## __ ## name ##_step(UNPAREN inputs, \ + module ## __ ## name ## _out *out, \ + module ## __ ## name ## _mem *mem) + +#define DEFINE_HEPT_NODE_NULLARY_STEP(module, name, inputs) \ + void module ## __ ## name ##_step(module ## __ ## name ## _out *out, \ + module ## __ ## name ## _mem *mem) + +/* FIXME remove when Heptagon's pervasives.h has been fixed. */ +typedef char * string; + +#endif /* HEPT_FFI */ diff --git a/cours/audio/main.c b/cours/audio/main.c new file mode 100644 index 0000000..d4e529a --- /dev/null +++ b/cours/audio/main.c @@ -0,0 +1,153 @@ +#include +#include +#include + +#include +#include + +#include "audio.h" +#include "vcd.h" + +const size_t sample_rate = Audio__period; + +void die(const char *message) { + fprintf(stderr, message); + exit(EXIT_FAILURE); +} + +SNDFILE *file_out = NULL; + +int main(int argc, char** argv) +{ + Audio__main_mem mem; + Audio__main_out res; + SDL_AudioSpec spec; + SDL_AudioDeviceID dev; + int opt = -1; + bool quiet = false; + size_t max_sec = SIZE_MAX; /* largest value of type size_t */ + const char *filename = NULL; + Uint32 buffered; + + while ((opt = getopt(argc, argv, "ho:qm:t:")) != -1) { + switch (opt) { + case 'h': + printf("Usage: %s OPTIONS\n", argv[0]); + printf("Options:\n"); + printf(" -o write samples to \n"); + printf(" -q do not play sound\n"); + printf(" -m play for seconds\n"); + printf(" -t dump traces in \n"); + printf(" -h display this message\n"); + return 0; + case 'q': + quiet = true; + break; + case 'o': + filename = optarg; + break; + case 'm': + max_sec = atoi(optarg); + break; + case 't': + hept_vcd_init(optarg, VCD_TIME_UNIT_US, 20); + break; + default: + fprintf(stderr, "Unknown option '%c'\n", opt); + exit(EXIT_FAILURE); + } + } + + if (SDL_Init(SDL_INIT_AUDIO) < 0) + die("Could not initialize SDL2\n"); + + /* Specification of requested output device. */ + bzero(&spec, sizeof spec); + spec.freq = sample_rate; /* Samples per second */ + spec.format = AUDIO_F32; /* Sample format: IEEE-754 32 bits */ + spec.channels = 2; /* Two channels */ + spec.samples = 4096; /* Buffers sized 4 KiB */ + spec.callback = NULL; + + if (!(dev = SDL_OpenAudioDevice(NULL, 0, &spec, NULL, 0))) + die("Could not open audio device\n"); + + if (filename != NULL) { + /* Specification of requested output file, if any. */ + SF_INFO info_out; + bzero(&info_out, sizeof info_out); + info_out.channels = 2; /* Two channels */ + info_out.samplerate = sample_rate; /* Samples per second */ + info_out.format = SF_FORMAT_WAV | SF_FORMAT_PCM_16; /* File format */ + + if (!(file_out = sf_open(filename, SFM_WRITE, &info_out))) { + fprintf(stderr, "Could not open WAV file %s for writing\n", argv[1]); + SDL_Quit(); + exit(EXIT_FAILURE); + } + } + + Audio__main_reset(&mem); + float *buffer = calloc(spec.samples, sizeof *buffer); + SDL_PauseAudioDevice(dev, 0); + + /* Loop until we've produced the requested amount of samples, that is the + duration in seconds multiplied by the number of samples per second. This + number of samples shall be sent on each of both stereo channels. + + Each iteration sends spec.samples stereo samples to the audio device, + hence we halve it to get the number of generated samples per-channel. */ + for (size_t samples = 0; + samples < max_sec * sample_rate; + samples += spec.samples / 2) { + + /* Print sound progress. */ + printf("\rSent %08zu samples", samples); + if (max_sec != SIZE_MAX) { + printf(" (%2.0f%)", 100. * (double)samples / (max_sec * sample_rate)); + } + fflush(stdout); + + /* Exit immediately if requested, e.g., the user pressed Ctrl-C. */ + if (SDL_QuitRequested()) { + printf("\n"); + return 1; + } + + /* Step the node as much as necessary to fill a buffer. Each step produces + one stereo sample. */ + for (size_t i = 0; i < spec.samples; i += 2) { + Audio__main_step(&res, &mem); + buffer[i+0] = res.o.l; + buffer[i+1] = res.o.r; + } + + /* Send the generated sound to the sound card and/or file. */ + if (!quiet) + SDL_QueueAudio(dev, buffer, spec.samples * sizeof *buffer); + if (file_out) + sf_writef_float(file_out, buffer, spec.samples / 2); + + /* Throttle queued audio, otherwise we will certainly end up consuming all + available memory. */ + buffered = SDL_GetQueuedAudioSize(dev); + while (!quiet && buffered >= 1 << 22) { + SDL_Delay(50); + buffered = SDL_GetQueuedAudioSize(dev); + } + } + printf("\n"); + + /* Wait until the audio buffer is empty. */ + printf("Waiting for queue flush... "); fflush(stdout); + while ((buffered = SDL_GetQueuedAudioSize(dev)) != 0) + SDL_Delay(50); + printf("done.\n"); + + free(buffer); + if (file_out) + sf_close(file_out); + SDL_Quit(); + + return 0; +} diff --git a/cours/audio/mathext.c b/cours/audio/mathext.c new file mode 100644 index 0000000..17b4b42 --- /dev/null +++ b/cours/audio/mathext.c @@ -0,0 +1,55 @@ +#include +#include +#include + +/* Avoid Heptagon's math.h. I don't think there's a single place where to find + math.h on Apple-platforms. */ +#ifndef __APPLE__ +#include +#endif + +#include "mathext.h" + +void Mathext__float_step(int x, Mathext__float_out *o) { + o->o = (float)x; +} + +void Mathext__int_step(float x, Mathext__int_out *o) { + o->o = (int)x; +} + +void Mathext__floor_step(float x, Mathext__floor_out *o) { + o->o = floorf(x); +} + +void Mathext__sin_step(float x, Mathext__sin_out *o) { + o->o = sinf(x); +} + +void Mathext__cos_step(float x, Mathext__cos_out *o) { + o->o = cosf(x); +} + +void Mathext__atan2_step(float y, float x, Mathext__atan2_out *o) { + o->o = atan2f(y, x); +} + +void Mathext__pow_step(float x, float y, Mathext__pow_out *o) { + o->o = powf(x, y); +} + +void Mathext__hypot_step(float x, float y, Mathext__hypot_out *o) { + o->o = hypotf(x, y); +} + +void Mathext__sqrt_step(float x2, Mathext__sqrt_out *o) { + o->o = sqrtf(x2); +} + +void Mathext__modulo_step(int x, int y, Mathext__modulo_out *o) { + o->o = x % y; +} + +void Mathext__piano_freq_of_key_step(int n, Mathext__piano_freq_of_key_out *o) { + o->f = (float)(pow(2, (float)(n - 49) / (float)12) * 440.); +} diff --git a/cours/audio/mathext.epi b/cours/audio/mathext.epi new file mode 100644 index 0000000..daf1fd5 --- /dev/null +++ b/cours/audio/mathext.epi @@ -0,0 +1,16 @@ +external fun float(x : int) returns (o : float) +external fun int(x : float) returns (o : int) +external fun floor(x : float) returns (o : float) + +external fun sin(x : float) returns (o : float) +external fun cos(x : float) returns (o : float) +external fun atan2(y : float; x : float) returns (o : float) +external fun hypot(x : float; y : float) returns (o : float) +external fun sqrt(x2 : float) returns (o : float) +external fun pow(x : float; y : float) returns (o : float) + +external fun modulo(x : int; y : int) returns (o : int) + +external fun piano_freq_of_key(k : int) returns (f : float) + +const pi : float = 3.14115 diff --git a/cours/audio/mathext.h b/cours/audio/mathext.h new file mode 100644 index 0000000..cf2f285 --- /dev/null +++ b/cours/audio/mathext.h @@ -0,0 +1,27 @@ +#ifndef MATHEXT_H +#define MATHEXT_H + +#include "stdbool.h" +#include "assert.h" +#include "pervasives.h" + +#include "hept_ffi.h" + +DECLARE_HEPT_FUN(Mathext, float, (int), float o); +DECLARE_HEPT_FUN(Mathext, int, (float), int o); +DECLARE_HEPT_FUN(Mathext, floor, (float), float o); + +DECLARE_HEPT_FUN(Mathext, sin, (float), float o); +DECLARE_HEPT_FUN(Mathext, cos, (float), float o); +DECLARE_HEPT_FUN(Mathext, atan2, (float, float), float o); +DECLARE_HEPT_FUN(Mathext, hypot, (float, float), float o); +DECLARE_HEPT_FUN(Mathext, sqrt, (float), float o); +DECLARE_HEPT_FUN(Mathext, pow, (float, float), float o); + +DECLARE_HEPT_FUN(Mathext, modulo, (int, int), int o); + +DECLARE_HEPT_FUN(Mathext, piano_freq_of_key, (int), float f); + +static const float Mathext__pi = 3.14115; + +#endif /* MATHEXT_H */ diff --git a/cours/audio/mathext_types.h b/cours/audio/mathext_types.h new file mode 100644 index 0000000..00a5ee7 --- /dev/null +++ b/cours/audio/mathext_types.h @@ -0,0 +1,4 @@ +#ifndef MATHEXT_TYPES_H +#define MATHEXT_TYPES_H + +#endif /* MATHEXT_TYPES_H */ diff --git a/cours/audio/vcd.c b/cours/audio/vcd.c new file mode 100644 index 0000000..0bcbce2 --- /dev/null +++ b/cours/audio/vcd.c @@ -0,0 +1,75 @@ +#include "vcd.h" + +#include +#include +#include + +#include "vcd_lib.h" + +vcd_file_t *vcd = NULL; + +bool enabled = false; + +void hept_vcd_cleanup() { + if (vcd) { + printf("[vcd] saving trace\n"); + vcd_file_write(vcd); + vcd_file_free(vcd); + } +} + +void hept_vcd_init(const char *filename, + size_t time_number, + vcd_time_unit_t unit) { + if (vcd) { + fprintf(stderr, "[vcd] initialization has already been performed\n"); + } else { + vcd = vcd_file_alloc(filename, time_number, unit); + assert (vcd); + if (atexit(hept_vcd_cleanup)) { + perror("[vcd] atexit() failed"); + exit(EXIT_FAILURE); + } + printf("[vcd] will save trace to %s\n", filename); + } +} + +static inline void trace_samples(vcd_signal_t **signal, + const char *name, vcd_signal_type_t type, + void *samples, size_t count) { + if (!vcd) + return; + + if (!*signal) { + *signal = vcd_signal_alloc(name, type, 1 << 17); + if (!vcd_file_add_signal(vcd, *signal)) { + perror("vcd_file_add_signal()\n"); + exit(EXIT_FAILURE); + } + } + vcd_add_samples(*signal, samples, count); +} + +DEFINE_HEPT_NODE_RESET(Vcd, trace_bool) { + mem->signal = NULL; +} + +DEFINE_HEPT_NODE_STEP(Vcd, trace_bool, (string name, int v)) { + trace_samples(&mem->signal, name, VCD_SIGNAL_TYPE_BOOL, &v, 1); +} + +DEFINE_HEPT_NODE_RESET(Vcd, trace_int) { + mem->signal = NULL; +} + +DEFINE_HEPT_NODE_STEP(Vcd, trace_int, (string name, int v)) { + trace_samples(&mem->signal, name, VCD_SIGNAL_TYPE_INT, &v, 1); +} + +DEFINE_HEPT_NODE_RESET(Vcd, trace_float) { + mem->signal = NULL; +} + +DEFINE_HEPT_NODE_STEP(Vcd, trace_float, (string name, float v)) { + trace_samples(&mem->signal, name, VCD_SIGNAL_TYPE_FLOAT, &v, 1); +} diff --git a/cours/audio/vcd.epi b/cours/audio/vcd.epi new file mode 100644 index 0000000..20eb209 --- /dev/null +++ b/cours/audio/vcd.epi @@ -0,0 +1,6 @@ +(* A basic library for producing VCD files from Heptagon code, typically used + for debugging purposes. *) + +external node trace_bool(name : string; v : bool) returns () +external node trace_int(name : string; v : int) returns () +external node trace_float(name : string; v : float) returns () diff --git a/cours/audio/vcd.h b/cours/audio/vcd.h new file mode 100644 index 0000000..f8e8920 --- /dev/null +++ b/cours/audio/vcd.h @@ -0,0 +1,18 @@ +#ifndef VCD +#define VCD + +#include +#include + +#include "hept_ffi.h" +#include "vcd_lib.h" + +void hept_vcd_init(const char *filename, + size_t time_number, + vcd_time_unit_t unit); + +DECLARE_HEPT_NODE(Vcd, trace_bool, (string, int),, vcd_signal_t *signal); +DECLARE_HEPT_NODE(Vcd, trace_int, (string, int),, vcd_signal_t *signal); +DECLARE_HEPT_NODE(Vcd, trace_float, (string, float),, vcd_signal_t *signal); + +#endif /* VCD */ diff --git a/cours/audio/vcd_lib.c b/cours/audio/vcd_lib.c new file mode 100644 index 0000000..d9b329c --- /dev/null +++ b/cours/audio/vcd_lib.c @@ -0,0 +1,198 @@ +#include "vcd_lib.h" + +#include +#include +#include +#include +#include +#include + +#include "buffer.h" + +size_t vcd_sizeof_signal_type(vcd_signal_type_t type) { + switch (type) { + case VCD_SIGNAL_TYPE_BOOL: + return sizeof(int); + case VCD_SIGNAL_TYPE_INT: + return sizeof(int); + case VCD_SIGNAL_TYPE_FLOAT: + return sizeof(float); + } +} + +typedef struct vcd_signal { + char *name; + vcd_signal_type_t type; + buffer_t *samples; +} vcd_signal_t; + +vcd_signal_t *vcd_signal_alloc(const char *name, + vcd_signal_type_t type, + size_t initial_buffer_size) { + vcd_signal_t *res = malloc_checked(sizeof *res); + res->name = strdup_checked(name); + res->type = type; + res->samples = + buffer_alloc(initial_buffer_size * vcd_sizeof_signal_type(type)); + return res; +} + +void vcd_signal_free(vcd_signal_t *signal) { + assert (signal); + + buffer_free(signal->samples); + free(signal->name); + free(signal); +} + +void vcd_add_samples(vcd_signal_t *signal, void *samples, size_t count) { + assert (signal); + assert (samples); + buffer_write(signal->samples, + samples, + count * vcd_sizeof_signal_type(signal->type)); +} + +const char *vcd_time_unit_repr(vcd_time_unit_t u) { + const char *table[] = { "s", "ms", "us", "ns", "ps", "fs" }; + assert (VCD_TIME_UNIT_S <= u && u <= VCD_TIME_UNIT_FS); + return table[u]; +} + +typedef struct vcd_file { + char *filename; + buffer_t *signals; + vcd_time_unit_t time_unit; + size_t time_unit_factor; +} vcd_file_t; + +vcd_file_t *vcd_file_alloc(const char *filename, + vcd_time_unit_t time_unit, + size_t time_unit_factor) { + assert (filename); + + vcd_file_t *vcd = malloc_checked(sizeof *vcd); + vcd->filename = strdup_checked(filename); + vcd->time_unit = time_unit; + vcd->time_unit_factor = time_unit_factor; + vcd->signals = buffer_alloc(10 * sizeof(vcd_signal_t)); + + return vcd; +} + +void vcd_file_free(vcd_file_t *vcd) { + assert (vcd); + + /* Free buffers. */ + buffer_foreach (vcd_signal_t *, psig, vcd->signals) + vcd_signal_free(*psig); + buffer_free(vcd->signals); + + free(vcd->filename); + free(vcd); +} + +vcd_signal_t *vcd_file_lookup_signal(const vcd_file_t *vcd, const char *name) { + assert (vcd); + assert (name); + + vcd_signal_t *res = NULL; + + buffer_foreach (vcd_signal_t *, psig, vcd->signals) { + if (strcmp((*psig)->name, name) == 0) { + res = *psig; + break; + } + } + + return res; +} + +bool vcd_file_add_signal(const vcd_file_t *vcd, vcd_signal_t *signal) { + assert (vcd); + assert (signal); + + if (vcd_file_lookup_signal(vcd, signal->name)) + return false; + + buffer_write(vcd->signals, &signal, sizeof signal); + return true; +} + +bool vcd_file_write(vcd_file_t *vcd) { + FILE *f = fopen(vcd->filename, "w"); + + if (!f) + return false; + + time_t current_time; + time(¤t_time); + + fprintf(f, "$version Generated by vcd.c $end\n"); + fprintf(f, "$date %s $end\n", ctime(¤t_time)); + fprintf(f, "$timescale %zu %s $end\n", + vcd->time_unit_factor, + vcd_time_unit_repr(vcd->time_unit)); + + /* Dump signal declarations. */ + fprintf(f, "$scope module Top $end\n"); + buffer_foreach (vcd_signal_t *, psig, vcd->signals) { + fprintf(f, "$var "); + switch ((*psig)->type) { + case VCD_SIGNAL_TYPE_BOOL: + fprintf(f, "wire 1"); + break; + case VCD_SIGNAL_TYPE_INT: + fprintf(f, "integer %zu", 8 * sizeof(int)); + break; + case VCD_SIGNAL_TYPE_FLOAT: + fprintf(f, "real 32"); + break; + } + fprintf(f, " %p %s $end\n", (*psig), (*psig)->name); + } + fprintf(f, "$upscope $end\n"); + fprintf(f, "$enddefinitions\n"); + + /* Dump samples. */ + fprintf(f, "$dumpvars\n"); + + /* We maintain a pointer to the current sample in each buffer. */ + size_t signal_count = vcd->signals->occupancy / sizeof(vcd_signal_t *); + unsigned char **psamples = calloc(signal_count, sizeof(unsigned char *)); + assert (psamples); + for (size_t i = 0; i < signal_count; i++) + psamples[i] = ((vcd_signal_t **)vcd->signals->data)[i]->samples->data; + + /* We dump */ + bool active = true; + for (size_t step = 0; active; step++) { + fprintf(f, "#%zu\n", step); + active = false; + for (size_t i = 0; i < signal_count; i++) { + vcd_signal_t *sig = ((vcd_signal_t **)vcd->signals->data)[i]; + if (psamples[i] < sig->samples->data + sig->samples->occupancy) { + active = true; + switch (sig->type) { + case VCD_SIGNAL_TYPE_BOOL: + fprintf(f, "%d%p\n", (*(int *)psamples[i] ? 1 : 0), sig); + psamples[i] += sizeof(int); + break; + case VCD_SIGNAL_TYPE_INT: + fprintf(f, "r%d %p\n", *(int *)psamples, sig); + psamples[i] += sizeof(int); + break; + case VCD_SIGNAL_TYPE_FLOAT: + fprintf(f, "r%.16g %p\n", *(float *)psamples, sig); + psamples[i] += sizeof(float); + break; + } + } + } + } + + free(psamples); + + fclose(f); + return true; +} diff --git a/cours/audio/vcd_lib.h b/cours/audio/vcd_lib.h new file mode 100644 index 0000000..cc5189a --- /dev/null +++ b/cours/audio/vcd_lib.h @@ -0,0 +1,47 @@ +#ifndef VCD_LIB_H +#define VCD_LIB_H + +#include +#include + +typedef enum vcd_signal_type { + VCD_SIGNAL_TYPE_FLOAT, + VCD_SIGNAL_TYPE_INT, + VCD_SIGNAL_TYPE_BOOL +} vcd_signal_type_t; + +size_t vcd_sizeof_signal_type(vcd_signal_type_t); + +typedef struct vcd_signal vcd_signal_t; + +vcd_signal_t *vcd_signal_alloc(const char *name, + vcd_signal_type_t type, + size_t initial_buffer_size); +void vcd_signal_free(vcd_signal_t *signal); + +void vcd_add_samples(vcd_signal_t *signal, void *samples, size_t count); + +typedef enum vcd_time_unit { + VCD_TIME_UNIT_S, + VCD_TIME_UNIT_MS, + VCD_TIME_UNIT_US, + VCD_TIME_UNIT_NS, + VCD_TIME_UNIT_PS, + VCD_TIME_UNIT_FS, +} vcd_time_unit_t; + +const char *vcd_time_unit_repr(vcd_time_unit_t); + +typedef struct vcd_file vcd_file_t; + +vcd_file_t *vcd_file_alloc(const char *filename, + vcd_time_unit_t time_unit, + size_t time_unit_factor); +void vcd_file_free(vcd_file_t *); + +vcd_signal_t *vcd_file_lookup_signal(const vcd_file_t *vcd, const char *name); +bool vcd_file_add_signal(const vcd_file_t *vcd, vcd_signal_t *signal); + +bool vcd_file_write(vcd_file_t *); + +#endif /* VCD_LIB_H */ diff --git a/cours/audio/vcd_types.h b/cours/audio/vcd_types.h new file mode 100644 index 0000000..0512dd9 --- /dev/null +++ b/cours/audio/vcd_types.h @@ -0,0 +1,4 @@ +#ifndef VCD_TYPES +#define VCD_TYPES + +#endif /* VCD_TYPES */ diff --git a/cours/journal.org b/cours/journal.org index 767dc48..57184d3 100644 --- a/cours/journal.org +++ b/cours/journal.org @@ -18,3 +18,13 @@ cliquer sur le bouton /download/ pour télécharger une copie PDF. https://gitlab.inria.fr/synchrone/heptagon/-/blob/ec26be27b91f3e601b98b8b7e15e8d56d4b9afc7/manual/heptagon-manual.pdf +* Cours 2 <2025-10-06> + On traite des constructions de base du langage. +* Cours 3 <2025-10-13> + On traite des horloges et automates. +* Cours 4 <2025-10-20> + On traite des automates et tableaux. +* Cours 5 <2025-10-27> + On traite des tableaux et de la génération d'échantillons audios. +* Cours 6 <2025-11-04> + On traite du fonctionnement du projet. diff --git a/examens/2024/examen-2024.pdf b/examens/2024/examen-2024.pdf new file mode 100644 index 0000000..8bad2c2 Binary files /dev/null and b/examens/2024/examen-2024.pdf differ diff --git a/projet/sujet/sujet-projet.pdf b/projet/sujet/sujet-projet.pdf index 157bf6a..a93d392 100644 Binary files a/projet/sujet/sujet-projet.pdf and b/projet/sujet/sujet-projet.pdf differ diff --git a/tp/TP03.pdf b/tp/TP03.pdf index f0c0a0b..e4c2327 100644 Binary files a/tp/TP03.pdf and b/tp/TP03.pdf differ