Desenvolupament d'aplicacions d'àudio amb Minim

  • Francesc Martí Pérez

PID_00220370
Cap part d'aquesta publicació, incloent-hi el disseny general i la coberta, no pot ser copiada, reproduïda, emmagatzemada o transmesa de cap manera ni per cap mitjà, tant si és elèctric com químic, mecànic, òptic, de gravació, de fotocòpia o per altres mètodes, sense l'autorització prèvia per escrit dels titulars del copyright.

Introducció

El llenguatge de programació Processing va ser iniciat l'any 2001 amb la finalitat de crear una nova aplicació que permetés ensenyar a persones no programadores els fonaments de la programació en un context visual. Estava pensat per generar imatges (estàtiques o en moviment) que poguessin reaccionar a ordres introduïdes des del teclat de l'ordinador o el moviment del ratolí i poca cosa més. El seu ràpid èxit va empènyer a altres programadors a ampliar les seves funcionalitats mitjançant biblioteques, permeten que en aquests moments ja es puguin desenvolupar aplicacions amb vídeo, àudio, imatges 3D, visualització de dades, etc. amb Processing.
Actualment, en l'àmbit de l'àudio, la biblioteca Minim és possiblement la biblioteca d'àudio per Processing més completa que existeix. Aquesta serà la biblioteca en la qual ens centrarem en aquest tutorial. Existeixen també altres interessants biblioteques d'àudio de tipus contributed libraries (no incloses amb el programa i, per tant, s'han de descarregar i instal·lar a part) que permeten treballar amb MIDI, crear música algorítmica en temps real o fer servir el motor d'àudio de SuperCollider a les aplicacions de Processing. És molt senzill instal·lar aquestes llibreries. Simplement hem d'anar al menú Sketch > Import Library... > Add Library, cercar la llibreria a instal·lar i fer clic al botó Install. En pocs segons ja tindrem la biblioteca llesta per ser utilitzada.
En aquest tutorial començarem amb una introducció general a la programació orientada a objectes, per passar a continuació a veure com es treballa amb classes i objectes a Processing en particular. La clara comprensió dels conceptes introduïts en aquests apartats serà imprescindible per abordar posteriorment amb èxit la programació amb Minim. Com veurem, a Minim estarem contínuament carregant classes, definint objectes, cridant a mètodes o canviant els valors d'atributs, pel que una deficient interpretació d'aquestes idees limitarà molt l'objectiu d'aquest tutorial: arribar a desenvolupar aplicacions d'àudio amb Processing fent servir Minim.
Ja dins la biblioteca Minim, ens centrarem en unes entitats anomenades unitats generadores o UGens que, com veurem, estan dissenyades per treballar amb àudio en temps real i de forma intuïtiva. Aquestes entitats es troben presents en la pràctica totalitat de llenguatges de programació musicals o orientats a desenvolupar aplicacions sonores, pel que és possible que algun dels lectors ja estigui familiaritzat amb elles.
Finalment, estudiarem alguns exemples d'aplicacions d'àudio desenvolupades en Minim que ens permetrà albirar les possibilitats d'aquesta biblioteca de Processing.

1.Breu introducció a la programació orientada a objectes

1.1.Conceptes fonamentals de la programació orientada a objectes

La programació orientada a objectes (1) és un model o paradigma de programació. Dit d'una altra manera, la POO és una forma particular de pensar, dissenyar i programar una aplicació informàtica.
(1) POO o OOP, en les seves sigles en anglès.
Tot i que no és estrany llegir en diferents fonts que es tracta d'una ''nova'' forma de programar, el cert és que els conceptes clau d'aquest paradigma ja van ser implementats l'any 1967 en el llenguatge de programació Simula 67.
Així doncs, per començar, és important no confondre la programació orientada a objectes amb un llenguatge de programació orientat a objectes.
La POO és una filosofia de programació, mentre que un llenguatge de programació orientat a objectes és un llenguatge de programació que permet desenvolupar programes informàtics seguint aquesta filosofia.
De fet, són varis els llenguatges de programació que admeten aquest paradigma. Els llenguatges de programació que s'ajusten completament als principis que aquest model proposa, i contemplen la possibilitat de treballar exclusivament amb objectes (més endavant veurem què són aquests objectes), reben el nom de llenguatges purs, mentre que els llenguatges híbrids, normalment, són llenguatges que ja existien abans que la POO aconseguís tanta notorietat i que incorporen a les seves estructures tradicionals alguns mecanismes per treballar també amb objectes (per això també reben el nom de llenguatges multi-paradigma). En la categoria de llenguatges purs podem destacar els llenguatges de programació Java, Smalltalk i Eiffel, i com a llenguatges híbrids tenim, entre d'altres, C++, Object Pascal, Python i Visual Basic.
Sense entrar a analitzar-les, són vàries les raons que han fet que la POO sigui actualment el model de programació per excel·lència. Gairebé tothom coincideix que el codi és més fàcil de mantenir, de reutilitzar, permet crear sistemes més complexos, les aplicacions acostumen a ser més robustes i flexibles, facilita el treball en equip, etc. Però si hi ha una característica que els defensors d'aquesta forma de programar sempre subratllen és que és un model molt intuïtiu, inclús relacionat amb el món real. No en va, la POO va néixer amb la intenció de crear un paradigma de programació basat en una percepció del món real, de forma que qualsevol desenvolupament o problema informàtic es pogués pensar com un desenvolupament o problema del món real –fet que, evidentment, possibilita que aquests s'abordin i resolguin de forma més natural. Examinem això en detall.
Observem el món real que ens envolta. Podem dir que està ple d'objectes que es relacionen i comuniquen entre sí. El nostre cotxe, el nostre gat, el nostre ordinador o, inclús, nosaltres mateixos són objectes del món real en constant interacció. Analitzant en detall aquests objectes podem arribar a la conclusió que cada objecte del món real és una entitat independent, amb unes característiques (o estats) i uns comportaments. És a dir, tenen unes propietats que els fan ser únics i, a més, són capaços de realitzar accions sobre les seves propietats o les propietats d'altres objectes.
Exemple
Per exemple, imaginem que li demanem a la persona Roger F., propietari d'un telèfon mòbil Galaxy S4, que el descrigui. Ens podria dir que el seu telèfon és de la marca Samsung, que el model és Galaxy S4 I9505, que és de color negre i que en aquell moment no té bateria. També hauria d'afegir que amb el seu telèfon pot trucar, fer fotografies, que el pot encendre, apagar, etc. La marca, model, color i estat de la bateria serien les seves característiques, i que pot trucar, fer fotografies, encendre'l o apagar-lo, els seus comportaments. Tota aquesta anàlisi pot ser representada amb una taula següent:
Objecte: telèfon mòbil de Roger F.

Característiques

Marca: Samsung

Model: Galaxy S4 I9505

Color: Negre

Estat de la bateria: Esgotada

Comportaments

Trucar

Fer fotografies

Encendre

Apagar

Aquesta mateixa anàlisi, aquesta descomposició de l'objecte telèfon mòbil de Roger F. en característiques i comportaments, també es pot realitzar sobre el seu gos, la seva bicicleta o el seu cotxe.
Una altra qualitat fàcilment observable és que també podem trobar objectes dintre d'objectes.
Exemple
El volant del meu cotxe és un objecte que està dins, forma part, de l'objecte el meu cotxe.
Per tant, resumint, podem veure el món com un conjunt d'objectes interactuant entre sí, amb les seves característiques o estats i els seus comportaments.
Òbviament, es pot objectar que aquesta visió és una mica simplista, però no hem d'oblidar que estem modelant el món real (basant-nos en una anàlisi orientada a objectes, en aquest cas) i, per definició, un model sempre és una simplificació de la realitat.
Però, quina relació té tot això amb la POO? La relació és que la POO es basa en aquesta forma de veure el món real. Els objectes de programari (2) són conceptualment anàlegs als objectes del món real, amb les seves característiques, comportaments, i la capacitat per interactuar amb altres objectes. Als objectes de programari les característiques o estats reben el nom d'atributs (3) i els comportaments s'anomenen mètodes (4) .
Anem a veure un altre exemple.
Exemple
Modelarem un objecte del món real de tipus Cotxe com a objecte de programari, és a dir, crearem un objecte de programari equivalent a un cotxe particular real. Anteriorment hem vist com representar l'anàlisi d'un objecte real fent servir una taula (l'objecte telèfon mòbil de Roger F.). Ara, representarem visualment un objecte de programari amb un diagrama circular, imitant la forma d'una cèl·lula.
Figura 1. Representació d'un objecte de programari amb un diagrama circular.
Figura 1. Representació d'un objecte de programari amb un diagrama circular.
Com es veu en el diagrama, el cotxe de Roger F. és de la marca Mercedes, va a 50 Km/h i amb la tercera marxa (aquests serien els atributs de l'objecte) i, a més, pot accelerar, arrencar, frenar, canviar de marxa, etc. (els seus mètodes).
Aquesta forma de representar un objecte de programari ens ajuda a introduir dos nous conceptes importants:
1) Anàlogament al que succeeix amb els objectes reals, un objecte de programari és una unitat indissoluble, on els atributs i els mètodes estan intrínsecament units formant un tot.
2) Normalment, els atributs no són accessibles des de l'exterior de l'objecte (*): estan amagats a l'interior de l'objecte i només es pot accedir a ells mitjançant els mètodes.
Nota
És més una recomanació sobre com s'hauria d'accedir als atributs d'un objecte que una característica essencial de la POO. En alguns textos podem llegir, incorrectament, que tots els atributs (és a dir, totes les dades) d'un objecte són privades i que s'ha d'accedir a ells mitjançant els mètodes públics. Això no és cert – tot i que és la forma més correcta de fer-ho. Per exemple, en Processing, tots els atributs d'una classe són sempre públics, és a dir, es pot accedir directament a un atribut sense fer servir un mètode.
Exemple
Per exemple, nosaltres no accedim directament al motor del cotxe quan el volem posar en marxa. Disposem d'un mecanisme -d'un mètode- que ens permet canviar l'estat del cotxe d'apagat a encès, però sense accedir directament al motor.
Finalment, per donar per acabada aquesta introducció teòrica, és necessari introduir el concepte de classe. Al món real, quan sentim la paraula cotxe, sabem que algú està parlant d'un objecte que es mou, té rodes, seients, un volant, que necessita combustible per funcionar, etc. El concepte de cotxe, la idea de cotxe, no és un cotxe en sí, és un concepte abstracte que ens ajuda a classificar objectes del món real que tenen unes certes característiques. De fet, conscient o inconscientment, al món real estem classificant contínuament: això és un cotxe, això és una motocicleta, allò és un arbre, etc.
Continuant amb les analogies, a la POO també podem programar conceptes. A la POO, aquestes entitats reben el nom de classes i són un bloc de codi que funciona com a motlle o plantilla genèrica per crear objectes particulars de similars característiques. Aquesta definició de classe, ens porta automàticament a una nova definició d'objecte: un objecte és una instància d'una classe.
Tot això ho podem veure més clarament amb l'ajuda de la figura 2. En ell, la classe Cotxe serveix com a plantilla, com a model per generar instàncies de la classe (és a dir, objectes).
Figura 2. La classe Cotxe serveix com a plantilla per generar objectes de tipus Cotxe.
Figura 2. La classe Cotxe serveix com a plantilla per generar objectes de tipus Cotxe.

1.2.Treballant amb classes i objectes en Processing

Després d'aquesta breu introducció teòrica ja estem preparats per veure com es treballa amb classes i objectes a Processing.
Si una classe és un motlle o plantilla genèrica que permet crear objectes, sembla obvi, doncs, que el primer que necessitem és una classe que ens permeti generar aquests objectes. Començarem amb una classe molt senzilla que modela la idea de rectangle. El codi d'aquesta classe és el següent:
// Nom de la classe
class MyRectangle {
  // Atributs
  float xpos;
  float ypos;
  float xlength;
  float ylength;

  // Mètodes de la classe
  // Constructor
  MyRectangle(float tempXpos, float tempYpos, float tempXlength, float tempYlength) {
    xpos = tempXpos;
    ypos = tempYpos;
    xlength = tempXlength;
    ylength = tempYlength;
  }

  void display() {
    rect(xpos, ypos, xlength, ylength);
  }
}
El primer que podem veure amb aquest exemple és que una classe és simplement un bloc de codi. Anem ara a estudiar l'estructura general d'una classe a partir d'aquest codi.
Qualsevol classe, per senzilla o complexa que sigui, té quatre elements estructurals, que són:
1) Nom de la classe. El nom de la classe és el primer que trobem al bloc de codi.
class MyRectangle { 
Com podem veure al nostre exemple, el nom de la classe és MyRectangle. Tradicionalment, els noms de les classes acostumen a començar per majúscula, per diferenciar-los dels objectes i altres variables. Tot i que abans s'ha comentat que una classe té quatre elements estructurals, tècnicament parlant, només el nom de la classe és obligatori.
2) Atributs. La següent secció d'una classe és el seu llistat d'atributs. Ja hem vist quins podrien ser els atributs d'objectes com un telèfon mòbil o un cotxe. En el cas d'un rectangle, sembla obvi que la seva posició i les seves mides han d'estar al llistat d'atributs. Per exemple, també podem afegir a aquest llistat d'atributs el color del rectangle.
3) Mètodes. Finalment, trobem el llistat de mètodes de la classe. Com es pot veure, són funcions. En el nostre exemple tenim dos mètodes, el constructor i la funció display(). Més endavant veurem com accedir i utilitzar aquests mètodes.
4) Constructor. El constructor és un mètode especial. Com indica el seu nom, aquest mètode de la classe permet construir, inicialitzar objectes. El nom del constructor ha de ser obligatòriament igual al nom de la classe. Una classe pot tenir més d'un constructor, que es diferenciaran en els seus paràmetres.
Exemple
Per exemple, a la classe MyRectangle podem afegir un constructor que sempre construeixi rectangles de mides 10 x 10. Al ser la mida constant, aquest constructor no necessita rebre els paràmetres tempXlength i tempYlength. Ara, aquesta classe permet construir objectes de dues formes diferents. Serà l'usuari qui decidirà quina fer servir.
  // Constructor 1: construeix rectangles de mides variables
  MyRectangle(float tempXpos, float tempYpos, float tempXlength, float tempYlength) {
    xpos = tempXpos;
    ypos = tempYpos;
    xlength = tempXlength;
    ylength = tempYlength;
  }

  // Constructor 2: construeix rectangles de mides constants
  MyRectangle(float tempXpos, float tempYpos) {
    xpos = tempXpos;
    ypos = tempYpos;
    xlength = 10;
    ylength = 10;
  }
Un cop vist els components fonamentals d'una classe, necessitem veure com es creen i utilitzen els objectes, per entendre bé com funcionen.
Comencem estudiant aquest codi:
// Declarem un objecte de classe MyRectangle
MyRectangle myRect;

void setup() {
  size(400, 400);

  // Inicialitzem l'objecte cridant al seu constructor
  myRect = new MyRectangle(10, 10, 30, 50);

  // Cridem al mètode display() de l'objecte myRect per dibuixar el rectangle
  // a la finestra de l'aplicació
  myRect.display();
}

void draw() {
}

// Aquí va inserit el codi de la classe MyRectangle
D'aquest codi és necessari destacar els següents aspectes:
1) Declaració de l'objecte. Amb el codi:
MyRectangle myRect;
es declara l'objecte myRect de la classe MyRectangle. Com podem veure, es declara igual que declararíem una variable de tipus float o int, per exemple.
2) Inicialització de l'objecte. Un cop declarat l'objecte és necessari inicialitzar-lo, construir-lo amb el mètode constructor. Per fer això, s'ha de cridar al constructor després de la paraula reservada new.
  myRect = new MyRectangle(10, 10, 30, 50);
Inicialitzant un objecte estem determinant les seves característiques. En el nostre cas, els valors que passem al constructor provocaran que la cantonada superior esquerra del rectangle estarà situada en el punt (10, 10) i que el rectangle farà 30 píxels de llarg i 50 píxels d'alçada. Si revisem el codi de la classe MyRectangle, veiem que al cridar al constructor, aquesta funció simplement assigna aquests valors als atributs de l'objecte myRect:
    xpos = 10;
    ypos = 10;
    xlength = 30;
    ylength = 50;
Per tant, el constructor, tal i com el tenim definim, no dibuixa el rectangle a la finestra de l'aplicació.
3) Crida als mètodes de l'objecte. En el nostre exemple, és el mètode display() el que fa que l'objecte myRect es dibuixi a la finestra de l'aplicació. Per accedir a un mètode hem de fer servir un punt ''.'' entre el nom de l'objecte i el nom del mètode. Per tant, si tenim l'objecte myRect i aquest objecte té un mètode de nom display(), la forma que té aquest objecte d'accedir al seu mètode és:
  myRect.display();
Aleshores, de forma resumida, el que hem fet és assignar valors als atributs de l'objecte, amb el constructor, i amb el mètode display() dibuixar el rectangle a la finestra de l'aplicació.
    rect(10, 10, 30, 50);
Per tant, si executem el sketch, el resultat serà:
Figura 3. Finestra de l'aplicació.
Figura 3. Finestra de l'aplicació.
Si una classe és un motlle per fabricar objectes amb característiques similars, un cop creada la classe MyRectangle, no hem de tenir problemes per definir dos objectes, dues instàncies d'aquesta classe amb diferents propietats (en el nostre cas, mides i posició del rectangle).
// Declarem dos objectes de la classe MyRectangle
MyRectangle myRect00;
MyRectangle myRect01;

void setup() {
  size(400, 400);

  // Inicialitzem el dos objectes amb els seus constructors
  myRect00 = new MyRectangle(10, 10, 30, 50);
  myRect01 = new MyRectangle(60, 30, 80, 30);

  // Cridem al mètode display() dels objectes per dibuixar els rectangles
  // a la finestra de l'aplicació
  myRect00.display();
  myRect01.display();
}

void draw() {
}

// Aquí va inserit el codi de la classe MyRectangle
Com es pot veure, hem inicialitzat els dos objectes amb valors diferents:
  // Inicialitzem el dos objectes amb el constructor
  myRect00 = new MyRectangle(10, 10, 30, 50);
  myRect01 = new MyRectangle(60, 30, 80, 30);
Per tant, a l'executar el sketch no ens estranyem de veure que es dibuixen dos rectangles a la finestra de la aplicació.
Figura 4. Finestra de l'aplicació.
Figura 4. Finestra de l'aplicació.
Arribats a aquest punt, un podria pensar que també és possible dibuixar dos rectangles sense definir cap classe ni cap objecte. Correcte! Aquest exemple, a part que ens ha ajudat a entendre els conceptes teòrics sobre la POO abans introduïts, també serveix per comprendre que la POO és una forma de programar, un model de programació:
un mateix problema pot ser resolt fent servir la POO o un altre model de programació.
Exemple
A Processing, és una bona pràctica guardar les classes a diferents pestanyes. A part de que el codi sigui més fàcil de llegir, facilita que la classe es pugui reutilitzar fàcilment en un altre sketch, ja que es guarda en el disc dur en un nou fitxer.
Figura 5. Creació d'una nova pestanya.
Figura 5. Creació d'una nova pestanya.
Com es pot veure a la figura 6, al guardar la classe en una nova pestanya, es crea automàticament un fitxer al disc dur que conté el codi de la classe MyRectangle.
Figura 6. Carpeta del projecte amb el fitxer MyRectangle.pde.
Figura 6. Carpeta del projecte amb el fitxer MyRectangle.pde.
Ja hem vist un sketch amb una classe i dos instàncies d'aquesta classe. Per acabar aquesta secció, anem ara a definir una nova classe al nostre programa. Aquesta nova classe té el nom de MyEllipse:
// Nom de la classe
class MyEllipse {
  // Atributs
  float xpos;
  float ypos;
  float diameter;

  // Mètodes de la classe
  // Constructor
  MyEllipse(float tempXpos, float tempYpos, float tempDiameter) {
    xpos = tempXpos;
    ypos = tempYpos;
    diameter = tempDiameter;
  }

  void display() {
    ellipse(xpos, ypos, diameter, diameter);
  }

  void echo() {
    for (int i=0; i<5; i++) {
      float newDiameter = diameter/(i+2);
      ellipse(xpos, ypos, newDiameter, newDiameter);
    }
  }
}
Com es pot veure, és una classe molt senzilla i similar a la classe MyRectangle. L'única diferencia estructural a destacar és que aquesta classe té un mètode més, de nom echo().
Anem a definir i utilitzar una instància de la classe MyEllipse al nostre programa.
// Declarem els diferents objectes
MyRectangle myRect00;
MyRectangle myRect01;
MyEllipse myEllip;

void setup() {
  size(400, 400);

  // Inicialitzem els dos objectes de la classe MyRectangle amb els constructors
  myRect00 = new MyRectangle(10, 10, 30, 50);
  myRect01 = new MyRectangle(60, 30, 80, 30);

  // Inicialitzem també l'objecte myEllip
  myEllip = new MyEllipse(200, 200, 180);

  // Cridem al mètode display() dels objectes per dibuixar-los a la
  // finestra de l'aplicació
  myRect00.display();
  myRect01.display();
  myEllip.display();

  // Cridem al mètode echo() de l'objecte myEllip
  // Aquest mètode dibuixa el·lipses dins de l'el·lipse principal
  myEllip.echo();
}

void draw() {
}

// Aquí va inserit el codi de les classes MyRectangle i MyEllipse
Si executem aquest codi, el resultat hauria de ser:
Figura 7. Finestra de l'aplicació.
Figura 7. Finestra de l'aplicació.
Com es pot veure, tenim un senzill programa que fa servir dos objectes de tipus MyRectangle i un objecte de tipus MyEllipse. De fet, un programa pot tenir tantes classes i objectes com sigui necessari.
Un error típic del programador principiant és intentar fer servir un mètode amb un objecte que no té aquest mètode definit. Si afegim la següent línia de codi al final de la funció setup():
  myRect00.echo()
el resultat que obtindrem en executar el programa serà ''The function echo() does not exist''. Però podem pensar: ''és clar que existeix aquesta funció, si l'acabo de fer servir!''. Sí que s'ha fet servir, però amb un objecte de la classe MyEllipse. Aquest mètode no pertany a la classe MyRectangle i, per tant, no el podem fer servir amb cap objecte de la classe MyRectangle.
Exemple
Fent una analogia amb els exemples anteriors Cotxe i Telèfon mòbil, aquest error seria equivalent a dir que volem posar benzina a un telèfon. Sí que existeix un mètode que es diu posar benzina, però aquest mètode només es pot fer servir amb instàncies de la classe Cotxe, no amb instàncies de la classe Telèfon mòbil.
Amb això acabem aquesta breu introducció a la POO, i a la utilització de classes i objectes a Processing. Tenir molt clars aquests conceptes és fonamental per ara poder treballar de forma àgil i eficient amb la biblioteca d'àudio Minim creant objectes, modificant les seves propietats i connectant-los entre sí fent servir els seus mètodes.

2.La biblioteca d'àudio per Processing Minim

2.1.Què és Minim?

Part de l'èxit de Processing es deu a la quantitat de desenvolupadors que han contribuït a ampliar les seves funcionalitats a través de biblioteques. Existeixen biblioteques de vídeo, d'àudio, d'imatge 3D, d'animació, de visualització de dades,... tot un seguit d'eines que ajuden als programadors a desenvolupar aplicacions més potents i amb menys esforç.
Desenvolupada per Damien Di Fede, Minim és una biblioteca d'àudio de codi lliure que permet integrar de forma intuïtiva i senzilla àudio en projectes de Processing.
La biblioteca Minim arriba instal·lada en processing i només cal importar-la per poder-la utilitzar. Ho fem mitjançant Sketch > Import Library > Add Library > Minim.
Són en particular útils els exemples que el mateix autor ens presenta a la seva Quickstart guide. La seva pàgina de documentació també es caracteritza per ser molt completa i proporcionar nombrosos exemples de les funcionalitats de la biblioteca. Tots aquests recursos són ideals per complementar i ampliar aquesta introducció a Minim.

2.2.Treballant amb Minim

Abans d'entrar a descriure les característiques de la biblioteca Minim en detall, veiem un exemple que ens mostra com de senzill és utilitzar Minim en Processing.
/**
  * Minim. Exemple 1
  * Francesc Martí, martifrancesc@uoc.edu, 15-10-2014
  *
  * Aquest sketch simplement genera una ona de tipus sinusoïdal, a 440 Hz
  * i 0.5 d'amplitud
  * 
  */

// Importem els paquets necessaris de la biblioteca Minim
import ddf.minim.*;
import ddf.minim.ugens.*;

// Definim els objectes
Minim minim;
Oscil myWave;
AudioOutput out;

void setup() {
  size(512, 200);

  // Inicialitzem l'objecte de la classe Minim
  minim = new Minim(this);

  // Inicialitzem un oscil·lador amb forma d'ona de tipus sinusoïdal
  // amb freqüència 440 Hz i amplitud 0.5
  myWave = new Oscil( 440, 0.5f, Waves.SINE );

  // Fem servir el mètode getLineOut de l'objecte minim per inicialitzar un 
  // objecte AudioOutput
  out = minim.getLineOut();

  // Finalment, connectem l'oscil·lador a la sortida d'àudio
  myWave.patch(out);
}

void draw() {
}
Analitzem aquest codi en detall. Per fer servir la biblioteca Minim al nostre sketch, el primer que hem de fer és carregar els paquets de la biblioteca necessaris amb les comandes:
import ddf.minim.*;
import ddf.minim.ugens.*;
Normalment, una biblioteca està segmentada en paquets (5) , i un paquet és simplement un contenidor de classes que permet dividir una biblioteca en parts més petites. Per tant, aquest codi li està dient al programa que carregui totes les classes que hi ha als paquets ddf.minim i ddf.minim.ugens.
Aquest és un concepte important. En carregar les classes de Minim dins el sketch ja estem en disposició de construir objectes d'àudio. A la teoria hem vist que el primer pas, abans de crear objectes, és definir les classes. Aquesta feina –aquesta ''feina bruta'' de definir les classes– ja està feta. La biblioteca d'àudio Minim ja ens proporciona les classes necessàries per crear objectes i desenvolupar aplicacions amb àudio dins de Processing.
Una altra forma de carregar la biblioteca Minim al sketch és mitjançant el menú ''Sketch > Import Library > minim'', però, en aquest cas es carregarien tots els paquets -i alguns no són necessaris per al nostre programa.
import ddf.minim.spi.*;
import ddf.minim.signals.*;
import ddf.minim.*;
import ddf.minim.analysis.*;
import ddf.minim.ugens.*;
import ddf.minim.effects.*;
Seguidament, el sketch declara i inicialitza 3 objectes de Minim. Per començar, sempre serà necessari declarar i inicialitzar un objecte de la classe Minim (sí, hi ha una classe amb el mateix nom que la biblioteca). Aquest és un objecte especial, que posa en marxa el motor d'àudio de Minim i que, entre d'altre coses, s'encarrega de configurar les sortides d'àudio. Per tant, si volem que el nostre sketch produeixi so, necessitem crear obligatòriament un objecte de la classe Minim.
Com ja hem vist anteriorment, un objecte es declara igual que una variable i hem de fer servir el seu constructor per inicialitzar-lo.
// Declaració
Minim minim;

// ...

// Inicialització de l'objecte minim amb el seu constructor
minim = new Minim(this);
El constructor de l'objecte minim té un únic paràmetre, i aquest és sempre la paraula reservada this (dins d'un constructor, la paraula this és una referència a l'objecte actual).
El següent objecte que creem és un generador de so. En concret, un oscil·lador.
// Declaració
Oscil myWave;

// ...

// Inicialització de l'objecte myWave amb el seu constructor
myWave = new Oscil(440, 0.5f, Waves.SINE);
Com és fàcilment deduïble, els tres paràmetres d'aquest constructor són:
  • freqüència de l'oscil·lador (440 Hz),

  • amplitud de l'oscil·lador (0.5) i

  • forma d'ona de l'oscil·lador (ona de tipus sinusoïdal).

I l'últim objecte que necessitem és un objecte de tipus AudioOutput, que s'encarregarà d'enviar tot l'àudio que generi l'aplicació a la targeta de so de l'ordinador.
// Declaració
AudioOutput out;

// ...

// Inicialització
out = minim.getLineOut();
Fixem-nos que aquí la inicialització és una mica diferent (no fem servir directament la paraula new). És mitjançant el mètode getLineOut() de l'objecte minim que aconseguim inicialitzar la sortida d'àudio. Per defecte, la crida al mètode getLineOut() sense paràmetres ens proporciona una sortida d'àudio en estèreo, a 16 bits, una velocitat de mostreig de 44.100 Hz i un buffer de 1.024 mostres. Però també podem cridar al mètode getLineOut() detallant les característiques que volem que tingui la sortida d'àudio. Per exemple:
out = minim.getLineOut(Minim.MONO, 512, 48000, 24);
proporciona una sortida d'àudio monofònica, amb un buffer de 512 mostres, una velocitat de mostreig de 48 Khz i una quantificació de 24 bits.
Així doncs, tenim un oscil·lador (l'objecte myWave) i una sortida d'àudio (l'objecte out). Anàlogament al que fem en el món real, el darrer pas és connectar l'oscil·lador a la sortida d'àudio. Això sempre ho farem amb el mètode patch().
  myWave.patch(out);
Si executem ara el programa podem escolar pels altaveus del nostre ordinador una ona sinusoïdal, de freqüència 440 Hz i amplitud 0,5.

2.3.Les UGens de Minim

Com ja s'ha comentat en la introducció, en aquest tutorial ens centrarem en unes entitats de Minim anomenades UGens.
A la fi de la dècada dels 50, Max Mathews, treballant en els laboratoris Bell, va desenvolupar MUSIC I (o simplement MUSIC), el primer programa per a la síntesi i processament digital de so per computadora. La forma en què es programava amb MUSIC (utilitzant unitats generadores) i, més tard, tota la família de programes i llenguatges de programació MUSIC-N va tenir tant d'èxit que ha estat adoptada pels moderns programes Max, Csound, Pure Data, Super Collider, Nyquist, Reaktor, Tassman, etc.
Una unitat generadora (UGen) (6) és un bloc de codi que funciona com a unitat de processament d'àudio digital.
(6) unit generator, en anglès.
Aquestes unitats disposen d'entrades i sortides i es poden connectar entre elles formant el que s'acostuma a anomenar un instrument.
Exemple
Oscil·ladors, filtres, amplificadors, etc. serien exemples d'unitats generadores. Tornant a les analogies entre el món real i el món de la programació, podem pensar que una unitat generadora equival a un pedal de guitarra o, inclús, la guitarra mateixa.
En la figura 8, cada component d'aquesta configuració de pedals d'efectes de guitarra equival a una UGen. Tenim una UGen que genera el so (la guitarra), una UGen que el distorsiona, una altra que li aplica un efecte de flanger, etc. En l'àmbit de la tecnologia musical, tot aquest conjunt d'unitats generadores connectades rep el nom d'instrument.
Figura 8. Es pot dissenyar instruments amb Minim de forma anàloga a com un guitarrista dissenya la seva configuració de pedals d'efectes.
Figura 8. Es pot dissenyar instruments amb Minim de forma anàloga a com un guitarrista dissenya la seva configuració de pedals d'efectes.
En l'àmbit de la programació és possible construir sistemes anàlegs. A Minim, les UGens són classes que generen o modifiquen l'àudio digital generat per una altra UGen, i els seus mètodes permeten la interconnexió entre elles, formant un instrument d'àudio digital. Aquestes unitats generadores inclouen generadors de so, efectes, operacions matemàtiques, envolupants i altres utilitats.
Anem a fer una breu descripció de cada una de les UGen i estudiar com estan agrupades.
2.3.1.Generadors de so
Com el seu nom indica, aquestes UGen generen senyals d'àudio digital fent servir diferents mètodes.
  • Noise: és una UGen que genera soroll de tipus blanc, rosa o marró.

  • Oscil: genera ones periòdiques a una determinada freqüència i amplitud. Poden fer-se servir com a LFO.

  • LiveInput: captura l'entrada d'àudio de la interfície d'àudio de l'ordinador.

  • FilePlayer: reprodueix l'àudio des d'un fitxer.

  • Sampler: és la UGen optimitzada per reproduir sons de curta durada.

  • GranulateSteady: UGen que permet treballar amb síntesi granular.

  • GranulateRandom: similar a la UGen GranulateSteday, produeix grans de so de longituds aleatòries.

2.3.2.Efectes
Els efectes són UGens que modifiquen el senyal d'àudio digital que l'envien una altra UGen.
  • Delay: genera repeticions amb retard del senyal d'entrada.

  • Pan: situa en una posició estereofònica el senyal monofònic d'entrada.

  • Balance: atenua el canal dret o esquerra d'un senyal estereofònic.

  • Gain: atenua o amplifica el senyal d'entrada.

  • MoogFilter: versió digital del filtre analògic 24 dB/octava de Moog. Pot configurar-se com a filtre passa baixos, passa alts o passa banda.

  • BitCrush: produeix distorsió al reduir la resolució del senyal d'entrada.

  • WaveShaper: utilitza el senyal d'entrada com a índex d'una taula d'ones.

  • Flanger: efecte basat en retards en la suma de dos senyals idèntics, aplicant-li a una d'elles un retard variable.

  • Vocoder: efecte basat en l'anàlisi i posterior síntesi de l'entrada d'àudio. És l'efecte que genera les típiques veus robotitzades.

2.3.3.Envolupants
Són UGens que permeten controlar propietats com l'amplitud d'un senyal o freqüència d'un LFO a temps real.
  • Line: genera una envolupant lineal entre els dos punts donats.

  • ADSR: genera una típica envolupant amb atac, decaïment, sosteniment i relaxació.

  • Damp: genera una envolupant amb atac i decaïment.

2.3.4.Matemàtiques
Aquestes UGens permeten realitzar operacions matemàtiques sobre el senyal d'entrada. Un senyal digital és bàsicament un llistat de valors numèrics i, per tant, es poden realitzar operacions matemàtiques sobre aquest llistat.
  • Abs: genera el valor absolut dels valors d'entrada.

  • Constant: serveix per definir un valor constant.

  • Midi2Hz: genera la freqüència equivalent en Hz d'un valor que representa una nota MIDI.

  • Multiplier: multiplica el senyal d'entrada per un valor d'amplitud.

  • Reciprocal: genera el valor recíproc del valor d'entrada.

  • Summer: suma tots els senyals d'entrada.

2.3.5.Utilitats
En aquesta categoria es trobarien les UGens que, més que tenir una funcionalitat en sí mateixes, ajuden a les altres UGen en el procés de construcció dels instruments.
  • Bypass: aquesta UGen permet configurar altres UGen en mode bypass.

  • EnvelopeFollower: analitza l'entrada d'àudio i genera un valor que representa la seva amplitud.

  • TickRate: permet canviar la velocitat del rellotge intern d'una UGen.

  • Sink: és una UGen similar al Summer, però, en comptes de sumar totes les entrades d'àudio, simplement genera silenci. Es fa servir per tenir operatives UGens que no generen so (com una EnvelopeFollower).

Per últim, com ja hem vist, la classe AudioOutput permet configurar les sortides d'àudio del instrument. Tècnicament, per la forma en que està programada aquesta classe de Minim, no és una UGen, tot i que a efectes pràctics la podem considerar com a tal.

2.4.La POO i el desenvolupament d'aplicacions d'àudio amb Minim

En aquest apartat farem convergir tots els conceptes introduïts fins ara aplicant-los al desenvolupament d'aplicacions d'àudio amb la biblioteca Minim. Hem estudiat la POO, definit què són les classes i els objectes, hem vist com treballar amb classes i objectes a Processing i, finalment, hem estudiat la biblioteca d'àudio Minim i unes classes especials anomenades UGens. De quina forma podem fer servir tots aquests elements per desenvolupar aplicacions d'àudio?
Tornem a l'exemple anterior (“Minim. Exemple 1”). Imaginem que encara no hem començat a programar i que volem crear una senzilla aplicació d'àudio que emeti el so d'una ona bàsica. Anem a veure com la POO ens permet pensar i dissenyar aquesta aplicació de forma intuïtiva, sense haver de recórrer a codi inicialment. Però, a més, també veurem com aquest paradigma inclús ens facilita el procés de generar el codi de l'aplicació quan s'arribi a aquesta etapa del desenvolupament.
A l'apartat anterior ja hem vist totes les UGens que tenim disponibles a Minim. Podem pensar que aquestes UGen són ''peces'', dispositius d'àudio reals que podem adquirir, configurar i connectar entre sí. Així que, un cop que tenim clar què volem construir, hem de pensar quines peces necessitem, com les hem de configurar i com les hem de connectar. Com podem veure, plantegem un problema informàtic com a problema del món real.
En el nostre cas, sembla clar que necessitem adquirir un oscil·lador, una sortida d'àudio i que hem de connectar l'oscil·lador a la sortida d'àudio. Esquemàticament, el disseny de l'aplicació és:
Figura 9.: Esquema de l'exemple “Minim. Exemple 1”.
Figura 9.: Esquema de l'exemple “Minim. Exemple 1”.
Un cop dissenyat l'instrument, podem passar a llistar quins serien les accions concretes a realitzar per construir-lo. En el nostre cas, aquestes accions poden ser:
1) Adquirir la carcassa de l'instrument.
2) Adquirir un oscil·lador.
3) Adquirir una sortida d'àudio.
4) Configurar la carcassa de l'instrument.
5) Configurar l'oscil·lador.
6) Configurar la sortida d'àudio.
7) Connectar l'oscil·lador a la sortida d'àudio.
Així doncs, ja tenim dissenyada i planificada la construcció de l'instrument al món real. Ara podem passar al món informàtic, i generar el codi que construirà un instrument informàtic equivalent.
En la taula 1 es pot veure en detall quin seria el codi en Processing, utilitzant la biblioteca Minim, que correspondria a cada acció que hauríem d'efectuar en el món real. Com es pot veure, la POO permet en moltes ocasions relacionar cada acció o configuració del món real amb la definició d'un objecte o crida d'una funció en el món informàtic. Això, no cal dir-ho, facilita molt l'acció de programar.
Taula 1
Món real
Processing amb Minim

Adquirir la carcassa del instrument

Minim minim;

Adquirir un oscil·lador

Oscil myWave;

Adquirir una sortida d'àudio

AudioOutput out;

Configurar la carcassa del instrument

minim = new Minim(this);

Configurar l'oscil·lador

myWave = new Oscil(440, 0.5f, Waves.SINE);

Configurar la sortida d'àudio

out = minim.getLineOut();

Connectar l'oscil·lador a la sortida d'àudio

myWave.patch(out);

Per fer aquest pas, la correspondència entre món real i món informàtic, també pot ser molt útil tractar d'escriure el codi que correspon a cada objecte i relació de l'esquema generat anteriorment. Per exemple, ara, a l'esquema, afegim les línies de codi més important que el programa ha d'incloure (configuració de l'oscil·lador, configuració de la sortida d'àudio i connexió dels dos elements).
Figura 10. Esquema de l'exemple “Minim. Exemple 1” amb el codi principal de l'aplicació.
Figura 10. Esquema de l'exemple “Minim. Exemple 1” amb el codi principal de l'aplicació.
D'aquesta manera, seguint aquests passos, arribem a generar el codi Minim. Exemple 1 anteriorment exposat.
Resumint, en aquest apartat hem vist amb un exemple de com plantejar el desenvolupament d'una aplicació d'àudio en Processing i Minim seguint la filosofia de la POO. Hem començat generant l'esquema de l'instrument, per passar a continuació a llistar quins serien els passos a realitzar en el món real per construir l'instrument dissenyat. Un cop finalitzades aquestes dues etapes, el procés de generar el codi de l'aplicació es simplifica sensiblement.

2.5.Accés a la documentació dels mètodes i atributs de les UGens

Anem ara a fer una mica més interessant l'instrument que hem programat. Estaria bé que el so del nostre instrument canviés en moure el ratolí o mitjançant el teclat de l'ordinador. L'instrument té dos objectes: un produeix un so (myWave) i l'altre fa que aquest so s'emeti pels altaveus de l'ordinador (out). Si volem que el so canviï al moure el ratolí o en prémer una tecla, tot sembla indicar que hem de trobar la forma de modificar les propietats de l'objecte myWave. Però quines són aquestes propietats? Com puc accedir a elles?
Un dels problemes més importants al que normalment s'enfronta un estudiant o qualsevol persona que s'inicia en el món de la programació és l'accés i correcta interpretació de la documentació de les eines que s'estan fent servir. En utilitzar biblioteques amb Processing és essencial saber fer servir la documentació que el desenvolupador de la biblioteca ens facilita. Difícilment trobarem mai cap tutorial que pugui analitzar totes i cadascuna de les propietats i mètodes de totes les classes d'una biblioteca, per la qual cosa, saber com accedir i interpretar aquesta informació és de vital importància pel programador. Aprofitarem les millores que volem implementar a l'instrument de l'exemple “Minim. Exemple 1” per mostrar com accedir i utilitzar la documentació de Minim.
Javadoc és la utilitat estàndard per documentar les biblioteques de Java i, per extensió, les de Processing. En el cas de la llibreria Minim, el desenvolupador d'aquesta biblioteca també ens ofereix una pàgina web amb documentació i nombrosos exemples, però aquí ens centrarem en la documentació en format Javadoc, ja que al ser l'estàndard, la seva comprensió ens facilitarà també el treball amb altres biblioteques.
El Javadoc d'aquesta biblioteca es troba a l'adreça http://code.compartmental.net/minim/javadoc/. Una vegada dins, podem veure que la part esquerra de la pàgina ens permet navegar pels diferents elements de la biblioteca i la part central ens mostra la informació de l'element seleccionat.
Exemple
Accedim a les classes UGen de Minim fent clic sobre el paquet ddf.minim.ugens.
Figura 11. Accés a la documentació de les UGens.
Figura 11. Accés a la documentació de les UGens.
Ara, podem veure que, a la part inferior de la columna esquerra, surt un llistat de totes les classes de tipus UGen de Minim. És el mateix llistat que hem vist a l'apartat “Les UGens de Minim”. Clicant sobre la classe Oscil accedim a la seva informació. En aquesta pàgina el desenvolupador de la biblioteca ens mostra tota la informació d'aquesta classe: els atributs, mètodes, constructors, etc.
Ara estem a prop de poder respondre les preguntes anteriors: quins són els atributs de l'objecte myWave? Com puc accedir a ells? Ja hem vist –i repetit diverses vegades!– que per norma general farem servir mètodes per accedir o canviar els valors de les propietats d'un objecte, així que anem directament a estudiar el seu llistat de mètodes.
Figura 12. Mètodes de la classe Oscil.
Figura 12. Mètodes de la classe Oscil.
Estudiant els mètodes d'aquesta classe veiem que entre els diferents mètodes tenim un que permet canviar la freqüència, un altre que permet canviar l'amplitud i un altre que permet canviar la forma d'ona de l'oscil·lador:
  • setFrequency (float hz): estableix la freqüència de l'oscil·lador. La nova freqüència vindrà determinada pel paràmetre hz.

  • setAmplitude (float newAmp): estableix l'amplitud de l'oscil·lador. La nova amplitud vindrà determinada pel valor newAmp.

  • setWaveform (Waveform theWaveForm): canvia la forma d'ona utilitzada per l'oscil·lador. En aquest cas, el paràmetre d'aquest métode són unes formes d'ona ja predefinides a Minim: Waves.PHASOR, Waves.QUARTERPULSE, Waves.SAW, Waves.SINE, Waves.SQUARE i Waves.TRIANGLE.

Així doncs, una opció per millorar l'instrument seria fer que canviï la freqüència i l'amplitud de l'oscil·lador amb el moviment del ratolí, i controlar la forma d'ona des del teclat de l'ordinador. L'esquema d'aquest instrument seria:
Figura 13. Esquema de l'exemple “Minim. Exemple 2”.
Figura 13. Esquema de l'exemple “Minim. Exemple 2”.
Anem a estudiar els nous elements que anem a introduir al programa. Per començar, ja sabem que les funcions mouseMoved() i keyPressed() de Processing permeten capturar accions del ratolí i del teclat de l'ordinador, respectivament. La funció mouseMoved() de Processing s'executa cada cop que l'aplicació detecta que el ratolí s'ha mogut dins la finestra de l'aplicació. El valor de les variables ja predefinides mouseX i mouseY són contínuament actualitzades amb la posició del punter del ratolí per Processing, mentre aquest estigui sobre la finestra de l'aplicació. mouseX conté la posició del punter del ratolí sobre l'eix horitzontal (l'eix x) de la finestra de l'aplicació i mouseY la posició sobre l'eix vertical (l'eix y). De totes maneres, normalment no podem fer servir aquests valors directament al sketch. Per exemple, si la variable mouseY val 150, no té sentit dir que l'amplitud de l'oscil·lador és 150, ja que està fora del seu rang de valors. Per solucionar això, fem servir la funció map(), que realitza una correspondència lineal entre dos segments de valors.
A la figura 14 es pot veure clarament com funciona aquesta funció. Si definim la funció map(mouseY, 0, 400, 0, 1), quan el punter del ratolí valgui 400, la funció map() ens retorna el valor 1, quan valgui 200, ens retorna el valor 0,5, etc.
Figura 14. Representació de la funció map().
Figura 14. Representació de la funció map().
En el nostre cas, el valor de mouseY varia entre 0 i height (l'alçada de la finestra de l'aplicació), i necessitem que quan mouseY valgui 0 l'amplitud sigui 1 i que quan mouseY valgui height l'amplitud sigui 0. El valor que ens retorna la funció map() és el valor que hem d'enviar al mètode setAmplitude() per canviar l'amplitud de l'oscil·lador. Per tant, les següents línies de codi estableixen l'amplitud de l'oscil·lador en funció de la posició vertical del ratolí.
  float amp = map(mouseY, 0, height, 1, 0);
  myWave.setAmplitude(amp);
Anàlogament, el codi:
  float freq = map(mouseX, 0, width, 150, 1000);
  myWave.setFrequency(freq);
permet controlar la freqüència de l'oscil·lador, entre 150 i 1.000 Hz, en funció de la posició del ratolí sobre l'eix horitzontal de la finestra de l'aplicació.
L'altra funció que també hem de tenir clara és la funció keyPressed(). Aquesta funció s'executa cada cop que pressionem una tecla. Així doncs, el que farem serà que les tecles 1 – 4 del teclat controlin la forma d'ona de l'oscil·lador. Per exemple, en clicar '4' l'oscil·lador passarà a generar una ona quadrada.
    myWave.setWaveform(Waves.SQUARE);
Finalment, aprofitarem també aquest exemple per introduir un codi que permet dibuixar l'ona que genera l'oscil·lador a la finestra de l'aplicació. Aquest codi es troba dins la funció draw(), ja que necessitem que el dibuix de l'ona s'actualitzi contínuament.
  // Dibuixem la forma d'ona que es genera a la finestra de l'aplicació
  for (int i = 0; i < out.bufferSize() - 1; i++)
  {
    line(i, 50  - out.left.get(i)*50, i+1, 50  - out.left.get(i+1)*50);
    line(i, 150 - out.right.get(i)*50, i+1, 150 - out.right.get(i+1)*50);
  }
Sobre aquest codi, és necessari comentar que, a Minim, un objecte de tipus AudioOutput sempre tindrà tres buffers: left, right i mix. El buffer left correspon al canal esquerre, el buffer right al dret i el buffer mix conté la mescla dels buffers left i right. En el cas d'estar reproduint un so monofònic, la informació dels canals left i right serà idèntica. Així que, bàsicament, aquest codi llegeix i escala els valors dels buffers left i right de l'objecte out i els dibuixa a la finestra de l'aplicació fent servir la funció line(). Com els valors dels buffers estan normalitzats entre -1 i 1, hem d'escalar aquests (multiplicant per 50 en aquest exemple) si volem que el dibuix de la forma d'ona sigui prou clar.
Així doncs, el codi del programa sencer, amb aquestes modificacions seria el següent:
/**
  * Minim. Exemple 2
  * Francesc Martí, martifrancesc@uoc.edu, 15-10-2014
  *
  * Aquest sketch permet controlar la freqüència, amplitud i forma d'ona d'un
  * oscil·lador mitjançant el ratolí i el teclat de l'ordinador.
  * La forma d'ona que es genera es dibuixa a la finestra de l'aplicació.
  *
  * Funcionament
  * mouseX: modifica la freqüència de l'oscil·lador entre 150 Hz i 1000 Hz.
  * mouseY: modifica l'amplitud de l'oscil·lador.
  *
  * tecla '1': forma d'ona de l'oscil·lador Sinusoïdal
  * tecla '2': forma d'ona de l'oscil·lador Triangular
  * tecla '3': forma d'ona de l'oscil·lador Dent de serra
  * tecla '4': forma d'ona de l'oscil·lador Quadrada
  *
  */

// Importem els paquets necessaris de la biblioteca Minim
import ddf.minim.*;
import ddf.minim.ugens.*;

// Definim els objectes
Minim minim;
Oscil myWave;
AudioOutput out;

void setup() {
  size(512, 200);

  // Inicialitzem l'objecte de la classe Minim
  minim = new Minim(this);

  // Inicialitzem un oscil·lador amb forma d'ona de tipus sinusoïdal
  // amb freqüència 440 Hz i amplitud 0.5
  myWave = new Oscil(440, 0.5f, Waves.SINE);

  // Fem servir el mètode getLineOut de l'objecte minim per inicialitzar un 
  // objecte AudioOutput
  out = minim.getLineOut();

  // Finalment, connectem l'oscil·lador a la sortida d'àudio
  myWave.patch(out);
}

void draw() {
  background(0);
  stroke(255, 255, 0);
  strokeWeight(1);

  // Dibuixem la forma d'ona que es genera a la finestra de l'aplicació
  for (int i = 0; i < out.bufferSize() - 1; i++)
  {
    line(i, 50  - out.left.get(i)*50, i+1, 50  - out.left.get(i+1)*50);
    line(i, 150 - out.right.get(i)*50, i+1, 150 - out.right.get(i+1)*50);
  }
}

void mouseMoved() {
  // Cada cop que el ratolí es mogui s'executa aquesta funció
  // Els mètodes de l'objecte myWave setAmplitude() i setFrequency() modifiquen
  // les propietats Amplitud i Freqüència de l'oscil·lador 

  float amp = map(mouseY, 0, height, 1, 0);
  myWave.setAmplitude(amp);

  float freq = map(mouseX, 0, width, 150, 1000);
  myWave.setFrequency(freq);
}

void keyPressed() {
  // Cada cop que es premi una tecla s'executa aquesta funció
  // Si es prem una tecla entre 1 i 4 s'executa aquest codi i es canvia la
  // forma d'ona de l'oscil·lador

  switch(key) {
  case '1': 
    myWave.setWaveform(Waves.SINE);
    break;

  case '2':
    myWave.setWaveform(Waves.TRIANGLE);
    break;

  case '3':
    myWave.setWaveform(Waves.SAW);
    break;

  case '4':
    myWave.setWaveform(Waves.SQUARE);
    break;

  default: 
    break;
  }
}

3.Estudi d'exemples

3.1.Reproductor d'àudio

Finalment, en aquest apartat veurem alguns exemples d'aplicacions d'àudio desenvolupades amb Processing i Minim. Com venim fent, primer dissenyarem l'instrument i després passarem a generar el codi.
En aquest primer exemple, anem a fer un sketch molt semblant als anteriors, però ara, en comptes de fer servir un oscil·lador com a generador de so, reproduirem un arxiu d'àudio.
Comencem veient quin és l'esquema d'aquest nou instrument. Com es pot apreciar, té un disseny molt semblant a l'instrument de l'exemple 2.
Figura 15. Esquema de l'exemple “Minim. Exemple 3”.
Figura 15. Esquema de l'exemple “Minim. Exemple 3”.
En aquesta aplicació utilitzem dos nous objectes que no hem vist encara: myFilePlayer i myFile. myFilePlayer és el reproductor i myFile és la cinta d'àudio que va dins del reproductor. Tal com fem per crear una sortida d'àudio, per crear l'objecte myFile ens valem de l'objecte minim, en comptes de fer servir la paraula clau new:
AudioRecordingStream myFile = minim.loadFileStream(fileName, 1024, true);
Aquí fileName és el nom de l'arxiu d'àudio, 1024 és la mida del buffer i el valor true especifica que volem carregar tot el so a la memòria. En cas de tenir un sor molt llarg o no disposar d'un ordinador amb prou memòria RAM és aconsellable posar false.
Són importants un parell de detalls del fitxer d'àudio que es reprodueix. Com el codi de l'aplicació és:
  fileName = "song.mp3";
hem de guardar el fitxer directament dins la carpeta del sketch o dins la carpeta data. Podem guardar el fitxer a qualsevol altra ubicació (inclús és possible reproduir fitxers d'àudio emmagatzemats a internet) però, aleshores, s'haurà de modificar aquest codi i especificar la ruta correcta on es troba el fitxer d'àudio.
La segona consideració a tenir present és que el fitxer d'àudio ha de tenir les mateixes característiques que la sortida d'àudio (nombre de canals, velocitat de mostreig i quantificació). Per tant, per fer funcionar el següent sketch hem de guardar a la carpeta del projecte (a l'arrel o dins la carpeta data) un arxiu d'àudio digital de nom ''song.mp3'' estèreo, a 44.100 Hz i 16 bits.
Finalment, la tecla 's' permet aturar o iniciar la reproducció de l'arxiu d'àudio.
/**
  * Minim. Exemple 3
  * Francesc Martí, martifrancesc@uoc.edu, 15-10-2014
  *
  * Aquest sketch simplement reprodueix un arxiu d'àudio digital.
  * La forma d'ona que es genera es dibuixa a la finestra de l'aplicació.
  *
  * Funcionament
  * tecla 's': atura la reproducció o la reprèn en mode loop si aquesta
  * estava aturada.
  *
  */

// Importem els paquets necessaris de la biblioteca Minim
// Atenció! Per fer servir la classe AudioRecordingStream hem d'importar
// també el paquet ddf.minim.spi

import ddf.minim.*;
import ddf.minim.ugens.*;
import ddf.minim.spi.*; 

// Definim els objectes
Minim minim;
FilePlayer myFilePlayer;
AudioRecordingStream myFile;
AudioOutput out;

// Aquesta variable guarda el nom de l'arxiu d'audio
String fileName;

void setup() {
  size(600, 200);

  // Inicialitzem l'objecte de la classe Minim
  minim = new Minim(this);

  // La variable fileName conté la ruta i el nom de l'arxiu d'àudio
  fileName = "song.mp3";

  // Inicialitzem l'objecte myFile
  // fileName és el nom de l'arxiu d'àudio, 1024 la mida del buffer
  // i 'true' indica que el so es carrega a la memòria
  myFile = minim.loadFileStream(fileName, 1024, true);

  // Inicialitzem el reproductor d'àudio myFilePlayer amb la "cinta" myFile  
  myFilePlayer = new FilePlayer(myFile);

  // Fem que l'arxiu d'àudio es reprodueixi en mode loop
  myFilePlayer.loop();

  // Fem servir el mètode getLineOut de l'objecte minim per inicialitzar un 
  // objecte AudioOutput
  out = minim.getLineOut();

  // Finalment, connectem el reproductor a la sortida d'àudio
  myFilePlayer.patch(out);
}

void draw()
{
  background(0);
  stroke(255, 255, 0);
  strokeWeight(1);

  // Dibuixem la forma d'ona a la finestra de l'aplicació
  for (int i = 0; i < out.bufferSize() - 1; i++)
  {
    line( i, 50  - out.left.get(i)*50, i+1, 50  - out.left.get(i+1)*50 );
    line( i, 150 - out.right.get(i)*50, i+1, 150 - out.right.get(i+1)*50 );
  }
}

void keyPressed() { 
  switch( key )
  {
  case 's': 
    // Depèn de si el so s'està reproduint o no,
    // l'aturem o iniciem la seva reproducció
    if ( myFilePlayer.isPlaying() )
    {
      // Aturem la reproducció
      myFilePlayer.pause();
    }
    else
    {
      // Comencem a reproduir de nou tot l'arxiu en mode loop
      myFilePlayer.loop();
    }
    break;

  default: 
    break;
  }
}

3.2.Efectes

En aquest exemple, afegirem un efecte a l'instrument anterior. La idea bàsica és, en comptes d'enviar directament el reproductor a la sortida d'àudio, enviar primer el reproductor a un efecte, i l'efecte a la sortida d'àudio. Esquemàticament, el nou instrument tindrà la estructura següent:
Figura 16. Esquema de l'exemple “Minim. Exemple 4”.
Figura 16. Esquema de l'exemple “Minim. Exemple 4”.
L'objecte que anem a inserir és un efecte de tipus MoogFilter. Aquesta UGen és una versió digital del famós filtre analògic 24 dB/octava de Moog i pot configurar-se com a filtre passa baixos, passa alts o passa banda.
El constructor d'aquest objecte necessita tres paràmetres:
  • la freqüència de tall (800 Hz),

  • la ressonància del filtre (0,5) i

  • especificar si volem que sigui un filtre passa baixos, passa alts o passa banda.

En el nostre cas, per defecte l'hem configurat com a passa baixos (MoogFilter.Type.LP). En el cas que el volguéssim configurar com a passa alts haurem d'escriure MoogFilter.Type.HP, i com a passa banda MoogFilter.Type.BP.
  moog = new MoogFilter(800, 0.5, MoogFilter.Type.LP);
Com es pot veure a la figura 16, el filtre el volem controlar amb el ratolí i el teclat de l'ordinador. Amb el ratolí controlarem la freqüència de tall (al nostre exemple anirà de 200 Hz a 12.000 Hz, però es poden fer servir altres valors) i la ressonància del filtre (que ha d'estar entre 0 i 1).
void mouseMoved(){
// ...

  // La freqüència de tall pot anar de 200 Hz a 12 KHz
  float freq = map(mouseX, 0, width, 200, 12000);
  float res  = map(mouseY, height, 0, 0, 1);

  moog.frequency.setLastValue(freq);
  moog.resonance.setLastValue(res);
I amb el teclat controlarem el tipus de filtre.
void keyPressed() { 
  switch(key)
  {
// ...

  case 'q':
    moog.type = MoogFilter.Type.LP;
    break;

  case 'w':
    moog.type = MoogFilter.Type.HP;
    break;

  case 'e':
    moog.type = MoogFilter.Type.BP;
    break;
    moog.type = MoogFilter.Type.LP; 
Finalment, per saber en tot moment quins són els valors del filtre, mitjançant la funció text() mostrarem els seus valors a la finestra de l'aplicació. Aquest codi es troba dins la funció draw(), ja que necessitem que aquesta informació s'actualitzi contínuament.
  text("Nom de l'arxiu d'àudio", 10, 225);
  text(fileName, 10, 245);
  text("Tipus de filtre: " + moog.type, 430, 225);
  text("Freqüència de tall: " + moog.frequency.getLastValue() + " Hz", 430, 245);
  text("Resonàcia del filtre: " + moog.resonance.getLastValue(), 430, 265);
Així doncs, el codi complet d'aquest nou exemple és:
/**
  * Minim. Exemple 4
  * Francesc Martí, martifrancesc@uoc.edu, 15-10-2014
  *
  * Aquest sketch reprodueix un arxiu d'àudio digital i li aplica un
  * filtre analògic 24 dB/octava de Moog.
  * La forma d'ona que es genera es dibuixa a la finestra de l'aplicació.
  *
  * Funcionament
  * mouseX: modifica la freqüència de tall del filtre (200 Hz a 12 Khz).
  * mouseY: modifica la ressonància del filtre.
  *
  * tecla 'q': filtre de tipus passa baixos
  * tecla 'w': filtre de tipus passa alts
  * tecla 'e': filtre de tipus passa banda
  *
  * tecla 's': forma d'ona de l'oscil·lador Quadrada
  *
  */

// Importem els paquets necessaris de la biblioteca Minim
import ddf.minim.*;
import ddf.minim.ugens.*;
import ddf.minim.spi.*;

// Definim els objectes
Minim minim;
FilePlayer myFilePlayer;
AudioRecordingStream myFile;
MoogFilter moog;
AudioOutput out;

// Aquesta variable guarda el nom de l'arxiu d'àudio
String fileName;

void setup() {
  size(640, 280);

  // Inicialitzem l'objecte de la classe Minim
  minim = new Minim(this);

  // La variable fileName conté la ruta i el nom de l'arxiu d'àudio
  fileName = "song.mp3";

  // Inicialitzem l'objecte myFile
  // fileName és el nom de l'arxiu d'àudio, 1024 la mida del buffer
  // i 'true' indica que el so es carrega a la memòria
  myFile = minim.loadFileStream(fileName, 1024, true);

  // Inicialitzem el reproductor d'àudio myFilePlayer amb la "cinta" myFile
  myFilePlayer = new FilePlayer(myFile);

  // Fem que l'arxiu d'àudio es reprodueixi en mode loop
  myFilePlayer.loop();

  // Construim un filtre de tipus Low Pass, freqüència de tall 800 Hz
  // i ressonància 0.5
  moog = new MoogFilter(800, 0.5, MoogFilter.Type.LP);

  // Fem servir el mètode getLineOut de l'objecte minim per inicialitzar un 
  // objecte AudioOutput
  out = minim.getLineOut();

  // Connectem el reproductor al filtre
  myFilePlayer.patch(moog);

  // Finalment, connectem el filtre al nostre out
  moog.patch(out);
}

void draw()
{
  background(0);
  stroke(255, 255, 0);
  strokeWeight(1);

  // Dibuixem la forma d'ona a la finestra de l'aplicació
  for (int i = 0; i < out.bufferSize() - 1; i++)
  {
    line( i, 50  - out.left.get(i)*50, i+1, 50  - out.left.get(i+1)*50 );
    line( i, 150 - out.right.get(i)*50, i+1, 150 - out.right.get(i+1)*50 );
  }

  // A la finestra de l'aplicació, mostrem informació sobre
  // els valors actuals del filtre
  text("Nom de l'arxiu d'àudio", 10, 225);
  text(fileName, 10, 245);
  text("Tipus de filtre: " + moog.type, 430, 225);
  text("Freqüència de tall: " + moog.frequency.getLastValue() + " Hz", 430, 245);
  text("Ressonància del filtre: " + moog.resonance.getLastValue(), 430, 265);
}

// Des del teclat controlem el reproductor (aturar i play)
// Ara també controlem el tipus de filtre
void keyPressed() { 
  switch(key)
  {
  case 's': 
    // Depèn de si el so s'està reproduint o no,
    // l'aturem o iniciem la seva reproducció
    if (myFilePlayer.isPlaying())
    {
      // Aturem la reproducció
      myFilePlayer.pause();
    }
    else
    {
      // Comencem a reproduir de nou tot l'arxiu en mode loop
      myFilePlayer.loop();
    }
    break;

  case 'q':
    moog.type = MoogFilter.Type.LP;
    break;

  case 'w':
    moog.type = MoogFilter.Type.HP;
    break;

  case 'e':
    moog.type = MoogFilter.Type.BP;
    break;

  default: 
    break;
  }
}

// Controlem la freqüència i la ressonància del filtre amb el moviment del ratolí
void mouseMoved() {
  // La freqüència de tall pot anar de 200 Hz a 12 KHz
  float freq = map(mouseX, 0, width, 200, 12000);
  float res  = map(mouseY, height, 0, 0, 1);

  moog.frequency.setLastValue(freq);
  moog.resonance.setLastValue(res);
}

3.3.Anàlisi FFT

En aquest exemple, anem a afegir una anàlisi espectral a l'instrument de l'exemple “Minim. Exemple 4”. L'estructura de l'instrument serà la mateixa, però ara afegirem una anàlisi espectral del so generat i la mostrarem a la finestra de l'aplicació.
El constructor de l'objecte fft necessita dos paràmetres:
  • la mida del buffer de l'objecte out i

  • la velocitat de mostreig a la que estem treballant.

  fft = new FFT(1024, 44100);
Tot i que tècnicament la classe FFT no és una UGen, podem veure que la forma de treballar amb ella no difereix del que hem vist fins ara.
Dins la funció draw() haurem d'anar fent l'anàlisi del so que es va generant. De l'anàlisi pròpiament dita s'encarrega el mètode forward().
  fft.forward(out.mix);
A subratllar que l'anàlisi s'efectua sobre la suma dels canals esquerre i dret del so, és a dir, sobre el buffer mix de la sortida d'àudio.
Així doncs, el codi complet d'aquest nou exemple és:
/**
  * Minim. Exemple 5
  * Francesc Martí, martifrancesc@uoc.edu, 15-10-2014
  *
  * Aquest sketch reprodueix un arxiu d'àudio digital i li aplica un
  * filtre analògic 24 dB/octava de Moog.
  * La forma d'ona que es genera es dibuixa a la finestra de l'aplicació.
  * L'anàlisi espectral del so generat també es mostra a la finestra de
  * l'aplicació.
  *
  * Funcionament
  * mouseX: modifica la freqüència de tall del filtre (200 Hz a 12 Khz).
  * mouseY: modifica la ressonància del filtre.
  *
  * tecla 'q': filtre de tipus passa baixos
  * tecla 'w': filtre de tipus passa alts
  * tecla 'e': filtre de tipus passa banda
  *
  * tecla 's': forma d'ona de l'oscil·lador Quadrada
  *
  */

// Importem els paquets necessaris de la biblioteca Minim
// Atenció! Per fer servir l'anàlisi espectral hem d'incloure
// el paquet ddf.minim.analysis
import ddf.minim.*;
import ddf.minim.ugens.*;
import ddf.minim.spi.*; 
import ddf.minim.analysis.*;

// Definim els objectes
Minim minim;
FilePlayer myFilePlayer;
AudioRecordingStream myFile;
MoogFilter moog;
FFT fft; // Definim l'objecte necessari per realitzar l'anàlisi FFT
AudioOutput out;

// Aquesta variable guarda el nom de l'arxiu d'àudio
String fileName;

void setup() {
  size(640, 400);

  // Inicialitzem l'objecte de la classe Minim
  minim = new Minim(this);

  // La variable fileName contindrà el nom de l'arxiu d'àudio
  fileName = "song.mp3";

  // Inicialitzem l'objecte myFile
  // fileName és el nom de l'arxiu d'àudio, 1024 la mida del buffer
  // i 'true' indica que el so es carrega a la memòria
  myFile = minim.loadFileStream(fileName, 1024, true);

  // Inicialitzem el reproductor d'àudio myFilePlayer amb la "cinta" myFile
  myFilePlayer = new FilePlayer(myFile);

  // Fem que l'arxiu d'àudio es reprodueixi en mode loop
  myFilePlayer.loop();

  // Construim un filtre de tipus Low Pass, freqüència de tall 800 Hz
  // i ressonància 0.5
  moog = new MoogFilter(800, 0.5, MoogFilter.Type.LP);

  // Construim l'objecte fft
  fft = new FFT(1024, 44100);

  // Fem servir el mètode getLineOut de l'objecte minim per inicialitzar un 
  // objecte AudioOutput
  out = minim.getLineOut();

  // Connectem el reproductor al filtre
  myFilePlayer.patch(moog);

  // Finalment, connectem el filtre al nostre out
  moog.patch(out);
}

void draw()
{
  background(0);
  stroke(255, 255, 0);
  strokeWeight(1);

  // Dibuixem la forma d'ona a la finestra de l'aplicació
  for (int i = 0; i < out.bufferSize() - 1; i++)
  {
    line(i, 50  - out.left.get(i)*50, i+1, 50  - out.left.get(i+1)*50);
    line(i, 150 - out.right.get(i)*50, i+1, 150 - out.right.get(i+1)*50);
  }

  // A la finestra de l'aplicació, mostrem informació sobre
  // els valors actuals del filtre
  text("Nom de l'arxiu d'àudio", 10, 225);
  text(fileName, 10, 245);
  text("Tipus de filtre: " + moog.type, 430, 225);
  text("Freqüència de tall: " + moog.frequency.getLastValue() + " Hz", 430, 245);
  text("Ressonància del filtre: " + moog.resonance.getLastValue(), 430, 265);

  // Fem l'anàlisi fft sobre el buffer mix
  fft.forward(out.mix);

  // Dibuixem l'espectre a la finestra de l'aplicació
  // Multipliquem el valor de l'anàlisi (getBand) per 8 per visualitzar
  // millor l'espectre
  for (int i = 0; i < out.bufferSize() - 1; i++)
  {
    // Espectre en vermell
    stroke(255, 0, 0);   
    line(i, height, i, height - fft.getBand(i)*8);
  }
}

// Des del teclat controlem el reproductor (aturar i play)
// Ara també controlem el tipus de filtre
void keyPressed() { 
  switch(key)
  {
  case 's': 
    // Depèn de si el so s'està reproduint o no,
    // l'aturem o iniciem la seva reproducció
    if ( myFilePlayer.isPlaying() )
    {
      // Aturem la reproducció
      myFilePlayer.pause();
    }
    else
    {
      // Comencem a reproduir de nou tot l'arxiu en mode loop
      myFilePlayer.loop();
    }
    break;

  case 'q':
    moog.type = MoogFilter.Type.LP;
    break;

  case 'w':
    moog.type = MoogFilter.Type.HP;
    break;

  case 'e':
    moog.type = MoogFilter.Type.BP;
    break;

  default: 
    break;
  }
}

// Controlem la freqüència i la ressonància del filtre amb el moviment
// del ratolí
void mouseMoved() {
  // La freqüència de tall pot anar de 200 Hz a 12 KHz
  float freq = map(mouseX, 0, width, 200, 12000);
  float res  = map(mouseY, height, 0, 0, 1);

  moog.frequency.setLastValue(freq);
  moog.resonance.setLastValue(res);
}
Figura 17. Finestra de l'aplicació de l'exemple “Minim. Exemple 5”.
Figura 17. Finestra de l'aplicació de l'exemple “Minim. Exemple 5”.

3.4.Síntesis additiva

En aquest exemple anem a desenvolupar un sintetitzador de síntesi additiva. La síntesi additiva és un tipus de síntesi que genera nous sons mitjançant la suma d'ones sinusoïdals (tons purs). Un dels problemes d'aquest tipus de síntesi és que es necessita un nombre elevat de tons purs per aconseguir resultats tímbricament interessants. Com la finalitat d'aquest exemple és purament pedagògica, treballarem només amb 10 sinusoides.
Com sempre, comencem dibuixant l'esquema de l'instrument. A més d'aquest nou objecte que permet sumar els diferents oscil·ladors, també afegirem un guany per controlar el volum de la suma dels tons purs. Tot i ser una estructura més complicada, en el fons estem fent com el guitarrista de la figura 8, cada vegada connectem més elements al nostre instrument.
Figura 18. Esquema de l'exemple “Minim. Exemple 6”.
Figura 18. Esquema de l'exemple “Minim. Exemple 6”.
Per començar, si volem sumar aquestes ones sinusoïdals, necessitem treballar amb un nou objecte que permeti aquesta suma. La sortida d'àudio només té una entrada, així que hem de tenir sumades els senyals abans d'enviar-les al out. Aquest objecte serà un objecte de tipus Summer i l'hem representat a l'esquema com a una taula de mescles.
  sum = new Summer();
En aquest instrument necessitem crear 10 oscil·ladors. Això ho fem amb un array d'objectes de tipus Oscil. Anàlogament a com es defineix un array de nombres int o float, també podem definir un array d'objectes. En aquest codi, es generen els 10 oscil·ladors i es connecten a l'objecte sum amb el mètode patch().
  for (int i = 0; i < numHarm ; i++) {
    myWave[i] = new Oscil(freqFonamental*(i+1), 0.1f, Waves.SINE );
    myWave[i].patch(sum);
  }
Tenint present que per defecte la freqüència del primer oscil·lador és 440 Hz, amb l'operació:
freqFonamental*(i+1)
generem un oscil·lador a 440 Hz, un altre a 880 Hz, a 1.320 Hz, 1.760 Hz, etc.
Un dels punts interessants de l'aplicació és com s'efectua la suma dels oscil·ladors. Hi ha configurades 4 formes diferents de sumar les ones sinusoïdals de l'aplicació, controlable des del teclat de l'ordinador amb les tecles 1-4:
1) Mètode 1: Amb aquest mètode, la suma dels harmònics tendeix a generar una ona amb forma dent de serra. Si 1 és l'amplitud de la freqüència fonamental f(0), aleshores f(1) = 1/2, f(2) = 1/4, f(3) = 1/5, etc.
2) Mètode 2: Amb aquest mètode, la suma dels harmònics tendeix a generar una ona amb forma quadrada. Si f(0) = 1, aleshores, f(1) = 0, f(2) = 1/3, f(3) = 0, f(4) = 1/5, f(5) = 0, etc.
3) Mètode 3: Amb aquest mètode, la suma dels harmònics tendeix a generar una ona amb forma triangular. Si f(0) # = #1, aleshores, f(1) = 0, f(2) = 1/9, f(3) = 0, f(4) = 1/25, etc.
4) Mètode 4: Aquest és el mètode que s'aplica per defecte al iniciar el programa. En aquest cas l'amplitud de tons els tons és la mateixa: f(0) = f(1) = f(2) = ... = f(9) = 0,1.
En aquesta aplicació, és molt pedagògic veure com la suma de tons purs (sinusoides) amb determinades amplituds generen ones amb forma de dent de serra, quadrada o triangular. De fet no ens hauria d'estranyar ja que, com ja sabem pel Teorema de Fourier, qualsevol ona periòdica es pot descompondre sempre com suma d'ones sinusoïdals. Per exemple, a la següent captura de la finestra del programa “Minim. Exemple 6” es pot veure com el resultat de la suma de 10 ones sinusoïdals amb les amplituds del mètode 3 s'acosta molt a una ona quadrada.
Figura 19. Finestra de l'aplicació de l'exemple “Minim. Exemple 6”.
Figura 19. Finestra de l'aplicació de l'exemple “Minim. Exemple 6”.
El codi sencer d'aquest exemple és el següent:
/**
  * Minim. Exemple 6
  * Francesc Martí, martifrancesc@uoc.edu, 15-10-2014
  *
  * Aquest sketch és un senzill sintetitzador de síntesis additiva.
  * El so generat consisteix en una ona sinusoïdal fonamental sumada als seus
  * primers 9 harmònics.
  *
  * Hi ha 4 formes de relacionar l'amplitud dels harmònics respecte a l'amplitud
  * de la fonamental.
  * Mètode Suma 1: f(0)=1, f(1)=1/2, f(2)=1/3, etc. La suma dels harmònics
  * tendeix a una ona amb forma Dent de serra.
  * Mètode Suma 2: f(0)=1, f(1)=0, f(2)=1/3, f(3)=0, etc. La suma dels harmònics
  * tendeix a una ona amb forma Quadrada.
  * Mètode Suma 3: f(0)=1, f(1)=0, f(2)=1/9, f(3)=0, f(4)=1/25. etc. La suma dels
  * harmònics tendeix a una ona amb forma Triangular.
  * Mètode Suma 4 (forma per defecte): f(0)=1, f(1)=1, f(2)=1, etc.
  *
  * La forma d'ona que es genera es dibuixa a la finestra de l'aplicació.
  * L'anàlisi espectral del so generat també es mostra a la finestra de
  * l'aplicació.
  *
  * Funcionament
  * mouseX: modifica la freqüència de la fonamental (entre 20 Hz i 2 KHz).
  * mouseY: modifica el guany (entre -30 i 0 dB).
  *
  * tecla '1': selecciona el Mètode Suma 1.
  * tecla '2': selecciona el Mètode Suma 2.
  * tecla '3': selecciona el Mètode Suma 3.
  * tecla '4': selecciona el Mètode Suma 4.
  *
  */

// Importem els paquets necessaris de la biblioteca Minim
import ddf.minim.*;
import ddf.minim.ugens.*;
import ddf.minim.analysis.*;

// Definim els objectes
Minim minim;
Oscil myWave[];
Summer sum; // Aquest objecte permet sumar els 10 oscil·ladors
Gain myGain;
FFT fft;
AudioOutput out;

// freqFonamental és la freqüència de la fonamental del so que es genera
float freqFonamental = 440.0f;

// Valor del guany
float dB;

// Mètode per sumar els harmònics. Per defecte el 4 (tots els oscil·ladors
// tenen la mateixa amplitud)
int metSuma=4;

// numHarm indica el nombre d'ones sinusoïdals que formarà el so generat
int numHarm = 10;

void setup() {
  size(740, 520);

  // Inicialitzem l'objecte de la classe Minim
  minim = new Minim(this);

  // Inicialitzem l'objecte sum, que permet sumar les 10 sinusoides  
  sum = new Summer();

  // Inicialitzem 'numHarm' oscil·ladors. 
  myWave = new Oscil[numHarm];

  // Creem els oscil·ladors i els connectem a l'objecte Summer
  for (int i = 0; i < numHarm ; i++) {
    myWave[i] = new Oscil(freqFonamental*(i+1), 0.1f, Waves.SINE );
    myWave[i].patch(sum);
  }

  // Inicialitzem l'objecte gain. Per defecte el seu valor és 0 dB, que equival
  // a no aplicar canvis sobre l'amplitud
  myGain = new Gain();

  // Construim l'objecte fft
  fft = new FFT(1024, 44100);

  // Fem servir el mètode getLineOut de l'objecte minim per inicialitzar un 
  // objecte AudioOutput
  out = minim.getLineOut();

  // Connectem el summer a l'objecte gain
  sum.patch(myGain);

  // Connectem el gain a la sortida d'àudio
  myGain.patch(out);
}

void draw() {
  background(0);
  stroke(255, 255, 0);
  strokeWeight(1);

  // Dibuixem la forma d'ona a la finestra de l'aplicació
  for (int i = 0; i < out.bufferSize() - 1; i++)
  {
    line( i, 50  - out.left.get(i)*50, i+1, 50  - out.left.get(i+1)*50 );
    line( i, 150 - out.right.get(i)*50, i+1, 150 - out.right.get(i+1)*50 );
  }

  // A la finestra de l'aplicació, mostrem informació sobre els valors actuals   
  // dels harmònics, el guany i el mètode per sumar els harmònics utilitzat
  fill(200);
  text("Freq (Hz)", 548, 230);  
  text("Amp", 664, 230);  
  fill(255);
  for (int i=0; i<10; i++) {
    text("F(" + i + "):   " + myWave[i].frequency.getLastValue(), 510, 250+(20*i));
    text(myWave[i].amplitude.getLastValue(), 660, 250+(20*i));
  }
  fill(0, 255, 255);
  text("Gain: " + dB + " dB", 510, 460);
  text("Mètode suma harmònics: " + metSuma, 510, 480);

  // Fem l'anàlisi fft sobre el buffer mix
  fft.forward(out.mix);

  // Dibuixem l'espectre a la finestra de l'aplicació
  // Multipliquem el valor de l'anàlisi (getBand) per 4 per visualitzar
  // millor l'espectre
  for (int i = 0; i < out.bufferSize() - 1; i++)
  {
    // Espectre en vermell
    stroke(255, 0, 0);   
    line(i, height, i, height - fft.getBand(i)*4);
  }
}

// Des del teclat controlem el mètode per sumar els harmònics
void keyPressed() { 
  switch(key)
  {
    // Dent de serra
  case '1':
    for (int i = 0; i < numHarm; i++) {
      myWave[i].setAmplitude(0.2/(i+1));
    }
    metSuma = 1;
    break;

    // Quadrada
  case '2':
    for (int i = 0; i < numHarm; i++) {
      myWave[i].setAmplitude((0.2/(i+1))*((i+1)%2));
    }
    metSuma = 2;
    break;

    // Triangular
  case '3':
    for (int i = 0; i < numHarm; i++) {
      myWave[i].setAmplitude((0.2/sq(i+1))*((i+1)%2));
    }
    metSuma = 3;
    break;

    // Tots els harmònics amb la mateixa amplitud
  case '4':
    for (int i = 0; i < numHarm; i++) {
      myWave[i].setAmplitude(0.1f);
    }
    metSuma = 4;
    break;

  default: 
break;
  }
}

void mouseMoved() {
  // Cada cop que el ratolí es mogui s'executa la funció mouseMoved()
  // Amb la posició horitzontal del punter del ratolí es controla la freqüencia
  // fonamental i els harmònics, i amb la posició vertical el guany
  dB = map(mouseY, 0, height, 0, -30);
  myGain.setValue(dB);

  float freq = map(mouseX, 0, width, 20, 2000);
  for (int i = 0; i < numHarm; i++) {
    myWave[i].setFrequency(freq*(i+1));
  }
}

Bibliografia

Budd, T. (1991). An introduction to object-oriented programming. Reading, Mass.; Wokingham: Addison-Wesley.
Di Fede, D. (2014). “Minim”. Disponible en: http://code.compartmental.net/tools/minim/.
Martí, F. (2012). “Audio en Processing”. Mosaic, 100. Disponible en: http://mosaic.uoc.edu/2012/10/01/audio-en-processing/.
Mills III, J. A.; Di Fede, D.; Brix, N. (2010). “Music programming in Minim”. Proceedings of the 2010 Conference on New Interfaces for Musical Expression (NIME 2010) (pág. 37-42). Sydney, Australia.
Oracle (2014). “Lesson: Object-oriented programming concepts (The Java™ Tutorials Learning the Java Language)”. Disponible en: http://docs.oracle.com/javase/tutorial/java/concepts/index.html.
Reas, C.; Fry, B. (2007). Processing: A programming handbook for visual designers and artists. Cambridge, Massachusetts: MIT Press.
Shiffman, D. (2008). Learning processing: A beginner's guide to programming images, animation, and interaction. Amsterdam; Boston: Morgan Kaufmann/Elsevier.