{{tag>audio processing chuck em}}
====== Séquences étranges ======
(Notes... 1er juin 2020)
Jouets sonores. Croquis de code pour des petites interfaces graphiques. Fonctionnels, mais sûrement avec des bugs surprises cachés dans les coins... Documentation minimale type prise de note
===== séquence étrange trois =====
juillet 2021 : publication de ce projet amélioré sur github : [[https://github.com/emoc/bouclette|bouclette]] \\
Boucles de samples type granulaire : à partir du même fichier sonore, boucler des petits fragments rapidement.
En vidéo, ça donne
{{http://emoc.org/ATELIER/processing3/2020_KI/sequences_etranges/sequence_etrange_trois_001/test2.webm?800x400|exemple vidéo}}
==== code ====
Un script processing pour l'interface graphique et un script chuck pour le granulateur, les deux communiquent par OSC
/*
Discussions entre processing et chuck
Lire en boucle plusieurs grains de sons d'un même fichier
Quimper, Dour Ru, 20200601 / pierre@lesporteslogiques.net
Processing 3.5.3 + ControlP5 2.2.6 + OscP5 0.9.9
chuck version: 1.4.0.1 linux (pulse) 64-bit
@ kirin / Debian Stretch 9.5
*/
import oscP5.*;
import netP5.*;
OscP5 oscP5;
NetAddress myRemoteLocation;
ArrayList grains = new ArrayList();
int id = 0;
float duree_morceau = 58.16; // en secondes
float dur;
PImage wave;
void setup() {
size (800, 400);
oscP5 = new OscP5(this, 12000);
myRemoteLocation = new NetAddress("127.0.0.1", 12012);
wave = loadImage("wf.png");
}
void draw() {
background(0);
image(wave,0,0);
// Montrer la position et le fragment bouclé
fill(128, 50);
stroke(128, 50);
strokeWeight(0.5);
dur = (((duree_morceau * 44100) / width) / ((mouseY / (float)height) * (3 * 44100)));
float pixels_par_seconde = (float)width / duree_morceau; // x pixel = 1 seconde
float duree_extrait = (1 - (mouseY / (float)height)) * 3 ; // 3 secondes max
dur = pixels_par_seconde * duree_extrait;
line(0, mouseY, width, mouseY);
rect(mouseX, 0, dur, height);
for (int i = grains.size() - 1; i >= 0; i--) {
Grain g = grains.get(i);
g.miseajour();
}
}
void keyPressed() {
if (key == 'p') ajouterGrain();
if (key == 'd') supprimerGrain(mouseX, mouseY);
if (key == 'c') resetGrain();
}
void ajouterGrain() {
id ++;
if (id < 100) {
grains.add( new Grain(mouseX, mouseY, id) );
OscMessage myMessage = new OscMessage("/sequence_etrange_trois");
float xn = mouseX / (float)width; // normaliser les valeurs
float yn = mouseY / (float)height; // normaliser les valeurs
myMessage.add(1);
myMessage.add(id);
myMessage.add(xn);
myMessage.add(1 - yn); // inverser, c'est plus intuitif, le zéro est en bas!
oscP5.send(myMessage, myRemoteLocation);
} else {
background(255, 0, 0);
println("trop de fragments!");
}
}
void supprimerGrain(float x, float y) {
for (int i = grains.size() - 1; i >= 0; i--) {
Grain g = grains.get(i);
if (dist(x, y, g.x, g.y) < 6) {
grains.remove(i);
OscMessage myMessage = new OscMessage("/sequence_etrange_trois");
myMessage.add(0);
myMessage.add(g.id);
myMessage.add(0);
myMessage.add(0);
oscP5.send(myMessage, myRemoteLocation);
}
}
}
void resetGrain() {
for (int i = grains.size() - 1; i >= 0; i--) {
Grain g = grains.get(i);
grains.remove(i);
OscMessage myMessage = new OscMessage("/sequence_etrange_trois");
myMessage.add(0);
myMessage.add(g.id);
myMessage.add(0);
myMessage.add(0);
oscP5.send(myMessage, myRemoteLocation);
}
id = 0;
}
class Grain {
float x, y; // coordonnées à l'écran (en pixels)
int id;
Grain(float x_, float y_, int id_) {
x = x_;
y = y_;
id = id_;
}
void miseajour() {
noFill();
stroke(255, 200);
strokeWeight(1);
line(x, y-5, x, y+5);
line(x-5, y, x+5, y);
}
}
// Debian 9.5 @ kirin
// Chuck 1.4.0.1 linux (pulse) 64-bit
// 20200601 / pierre@lesporteslogiques.net
OscIn oin; // définir le récepteur OSC
12012 => oin.port; // port de réception
OscMsg msg;
oin.addAddress( "/sequence_etrange_trois" );
int fragments[100]; // Pour stocker les id et les numéros de shred...
while(true) {
oin => now;
while (oin.recv(msg) != 0) {
msg.getInt(0) => int action;
msg.getInt(1) => int index;
msg.getFloat(2) => float start;
msg.getFloat(3) => float dur;
if (action == 0) { // Supprimer le shred associé à cet id
<<< "supprimer index " , index, "fragments : ", fragments[index] >>>;
Machine.remove(fragments[index]);
}
if (action == 1) { // Démarrer un nouveau shred
Shred s;
spork ~ grain(start, dur) @=> s;
<<< "Index : " , index, " > nouveau shred : " , s.id() >>>;
s.id() => fragments[index];
}
}
}
fun void grain(float start, float dur) {
SndBuf gra => dac;
me.dir() + "/kabuki_sound_effects.wav" => gra.read;
while(1) {
(dur * 3 * 44100) $ int => int gradur;
(start * gra.samples()) $ int => gra.pos;
if (gra.pos() < 0) 0 $ int => gra.pos;
gradur :: samp => now;
}
}
==== petites choses utiles ====
L'image de la forme d'onde est créée avec [[https://github.com/bbc/audiowaveform|audiowaveform]] (nécessaire d'indiquer la durée du son)
# 20200601 debian 9.5 @ kirin
audiowaveform -i ./son.wav -s 0 -e 58.16 --background-color 000000 --waveform-color dddddd --no-axis-labels -w 800 -h 100 -o wf.png
La capture vidéo est faite avec ffmpeg (cf. https://trac.ffmpeg.org/wiki/Capture/Desktop)
# ffmpeg version 3.2.14-1~deb9u1
# dans les controles de pulseaudio, dans l'onglet "Enregistrement" il faut régler "Monitor of audio interne analogique"
ffmpeg -video_size 800x400 -framerate 25 -f x11grab -i :0.0+465,21 -f pulse -ac 2 -i default test2.mp4
Les coordonnées de la fenêtre sont récupérées par (cf. https://unix.stackexchange.com/q/14159)
sleep 5s && xdotool getactivewindow getwindowgeometry
En fait ça n'est pas tout à fait la position mais je n'ai pas cherché plus loin, j'ai juste enlevé 22 pixels à la position verticale pour que ce soit à peu près bien cadré...
===== Ressources =====
* ChucK doc : https://chuck.cs.princeton.edu/doc/language/
* ChucK ugens : https://chuck.cs.princeton.edu/doc/program/ugen_full.html