{{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