Proyecto I&P 2023 - Sintetizador arpegiador / Grupo 6

De Casiopea
Proyecto I&P 2023 - Sintetizador arpegiador / Grupo 6


TítuloProyecto I&P 2023 - Sintetizador arpegiador / Grupo 6
Tipo de ProyectoProyecto de Taller, Proyecto de Curso
Palabras Claveproyectoarduino
Período2023-2023
AsignaturaInteracción y Performatividad 2023
Del CursoInteracción y Performatividad 2023
CarrerasDiseño
Alumno(s)Francisca Araya, Fauve Bellenger, Delia Madrid
ProfesorFranco Gnecco

Introducción

Este proyecto está basado en el Mosquito I, un sintetizador arpegiador basado en la librería Mozzi, la cual permite construir sonidos mucho más complejos, similares a los que se obtienen de los sintetizadores analógicos antiguos. Esta librería actualmente es la más usada para proyectos de creación de instrumentos musicales, debido a la robustez de su código y su versatilidad sonora. Para realizar este proyecto se hicieron algunas modificaciones para que pudiese sonar más fuerte. Junto con esto, se diseñó una caja contenedora robusta a base de trupán y acrílico, cercano a los sintetizadores DIY tradicionales.

Componentes

Circuito

  • Arduino Uno
  • (5) 10 kΩ Potenciómetros
  • (2) botones pulsadores
  • (1) luz led
  • (2) 1 kΩ Resistencia
  • (1) 330 Ω Resistencia
  • (1) Jack de audio 6,5 mm hembra

Caja Contenedora

  • Plancha de acrílico de 3mm
  • Plancha de MDF de 3mm

Funcionalidades

Este sintetizador puede ser alimentado con baterías de 9v, conexión a transformador o por vía USB. Posee conexión de audio de 6.5 mm por lo que se puede conectar a amplificadores, o parlantes, por medio de un adaptador para cables auxiliares de 3.5 mm.

Potenciómetros

Posee cinco potenciómetros que permite controlar el dispositivo, siendo cada uno controlador de distintos valores:

  1. Rate (Velocidad): Controla la velocidad de reproducción del secuenciador. Girándolo hacia abajo reproduce notas discretas en secuencia. Al subirlo, las notas se mezclan para crear formas de onda completamente nuevas.
  2. Legato: El segundo potenciómetro controla el legato o la duración de las notas. Girándolo más a la izquierda se producen notas cortas - sticatto - mientras que girándolo a la derecha se producen notas más largas.
  3. Pitch (Tono): Establece el tono base de la secuencia. El control de tono establece valores MIDI, por lo que aumenta/disminuye el tono en semitonos en lugar de un cambio de tono continuo.
  4. Phase (Fase): Al girar este control hacia la derecha se introduce un sutil efecto de fase. Técnicamente hablando, esto hace que los dos osciladores del Mosquito I estén ligeramente desafinados, que es lo que causa el efecto de fase.
  5. Filter (Filtro): Este mando controla la frecuencia de corte (cutoff) de un low pass filter. Girándolo a la izquierda se cortan las frecuencias altas produciendo un sonido más apagado, mientras que girándolo a la derecha se produce un sonido más brillante.

Botones

El sintetizador tiene más de veinte secuencias diferentes que puede reproducir por defecto. Los botones permiten seleccionar qué secuencia se está reproduciendo. Un botón te desplaza hacia arriba en la lista de secuencias y el otro hacia abajo.

Circuito y código

El proyecto original poseía un circuito RC que otorgaba un filtro controlador del ruido, pero este estaba pensado para amplificadores y no para parlantes portátiles. Por eso se decidió quitar ese circuito, que estaba compuesto por un capacitor cerámico de 33 nf, un condensador electrolítico de 100 uf y una resistencia de 200 Kohm. Este circuito se conectaba previamente al jack de audio para filtrar el sonido proveniente del sintetizador, por eso fue fácil quitarlo y que se mantuviera la integridad del circuito.

Paso a paso

Primer Paso

Potenciómetros

Conectar todos los potenciómetros a GND (extremo izquierdo) y a 5V (extremo derecho). El pin del medio va conectados a los pin análogos de la placa Arduino. En este caso se especifica a cual conexión corresponde cada uno y su función:

  • Pin A0 - Rate
  • Pin A1 - Legato
  • Pin A2 - Pitch
  • Pin A3 - Phase
  • Pin A4 - Filter

Aunque el circuito no contenga el circuito RC, el pin A4 sigue controlando la función de filtro, por lo que se decidió mantenerlo.

Botones

Este sintetizador posee 20 secuencias, siendo cada una seleccionable por medio de los botones; uno permite avanzar en la selección y otro retrodeder. Se conectan los botones pulsadores, el lado derecho que corresponde a 5V primero es conectado a una resistencia de 1Kohm y luego a 5V; este procedimiento se hace con los dos botones. Los GND van conectado a los siguientes pin:

  • Botón 1 - Pin D2: permite subir en la selección de secuencias
  • Botón 2 - Pin D3: permite bajar en la selección de secuencias

LED

Señala la velocidad con la que la secuencia es reproducida y está directamente ligado al potenciómetro del pin A0 - Rate. El LED va conectado por su cátodo hacia una resistencia de 330 ohm y luego al pin D4 de la placa. El ánodo va conectado a GND.

Jack de Audio

El jack de audio va conectado a GND desde el lado negativo y al pin 9 desde el lado positivo.

Segundo paso

Construcción de la caja contenedora

Caja contenedora de 17cmx17cm de ancho, con un alto de 8cm, compuesto de dos materiales; MDF, como mayor superficie cubierta y acrílico como cubierta y tapa central.

Cubierta Central

En esta parte se ubica principalmente, los orificios de las perillas del sintetizador,abajo se ubican los nombres, además se encuentran en la esquina superior derecha la ubicación del led, y en la esquina superior izquierda la de los botones.

C22 dmr.jpeg

Cubierta debajo

La parte debajo, posee seis partes por donde serian las aberturas en las cuales se encajarían las demás piezas.

C11 dmr.jpeg

Lado frontal 1

El primer lado frontal cumple la función de además de unirse a las cubiertas,es contenedor de aberturas en las que se encajarían los otros lados frontales. L1 f dmr.jpeg


Lado frontal 2

El segundo lado,cumple la función principal de encajar en los lados frontales 1 y en las cubiertas. L2 f dmr.jpeg


Lado frontal USB & JACK

Este lado en cuanto a diseño base es idéntico al lado frontal 1,con la diferencia que este posee los orificios en parte inferior; entradas usb y para el jack. LUSB f dmr.jpeg


Plano total

En la imagen se aprecia el plano total de lo que comprende las partes de la caja, estas puesta en una superficie de 41cmx76cm,el cual es el tamaño de la plancha mdf.

Cf 2 dmr.jpeg

Código

Código por Tim D "analalogsketchbook"

/*  Mosquito I
 *   by Tim D...
 *   analog.sketchbook.101@gmail.com
*/

#include <MozziGuts.h>
#include <Oscil.h>

#include <tables/saw8192_int8.h>
#include <tables/smoothsquare8192_int8.h>

#include <EventDelay.h>
#include <mozzi_midi.h>
#include <LowPassFilter.h>

#define CONTROL_RATE 128 // powers of 2 please

Oscil<SAW8192_NUM_CELLS, AUDIO_RATE> oscOne(SAW8192_DATA);       //audio oscillator
Oscil<SMOOTHSQUARE8192_NUM_CELLS, AUDIO_RATE> oscTwo(SMOOTHSQUARE8192_DATA); //audio oscillator
LowPassFilter lpf;                                                               //low pass filter

// for sequencing gain
EventDelay kGainChangeDelay;
char gain = 1;

// analog pins (for control pots)
// Note: Pin assignment is a bit arbitrary. This order worked fine for the way I laid out the pots
//       on my front panel, but you might find you need a different order to help with wire routing,
//       panel layout etc
const int OSC_ONE_PIN = 2;    // pitch
const int OSC_TWO_PIN=3;      // phase
const int MODA_PIN=0;         // rate
const int MODB_PIN=1;         // legato
const int MODC_PIN=4;         // filter

// digital pins
const int ledPin=4;           // LED
const int upButtonPin = 2;    // up push button
const int downButtonPin = 3;  // down push button

// global vars to handle push button functionality and debouncing
int upButtonState;
int lastUpState = LOW;
int downButtonState;
int lastDownState = LOW;
unsigned long lastUpDebounceTime = 0;    
unsigned long lastDownDebounceTime = 0;  
unsigned long debounceDelay = 50;        // the debounce time; increase if the output flickers

// division constants
// We're doing some common division operations ahead of time so that we can avoid division in the main loops 
// which will slow things down when Mozzi is trying to do its thing. Doing the division here allows us to use
// multiplication in the main loop which is much faster
const float DIV1023 = 1.0 / 1023.0; // division constant for pot value range
const float DIV10 = 1.0 / 10.0;     // division constant for division by 10

// global vars for the arpeggiator stuff
int sequence = 0;               // current sequence playing
int note = 0;                   // current note playing
const int numSequences = 21;    // total number of sequences (must match number of sequences in NOTES array)
const int numNotes = 8;         // total number of notes in each sequence (must all be the same and match length of each sequence in NOTES)

// sequences of midi notes to play back
// add new sequences or modify existing ones to suit
// just make sure you update the numSequences and numNotes variables above to match what you've done
const int NOTES[numSequences][numNotes] = {
                        {36,48,36,48,36,48,36,48},                    // up on two
                        {36,36,36,36,36,36,36,36},                    // flat quarters
                        {36,36,36,48,36,36,36,48},                    // up on four
                        {36,36,48,42,36,36,36,48},                    // dum da dum
                        {48,48,36,48,48,36,48,36},                    // two up one down
                        {36,48,36,48,36,48,36,48},                    // up down
                        {36,36,48,36,36,48,36,48},                    // up on three
                        {36,37,38,39,40,41,42,43},                    // chromatic up
                        {43,42,41,40,39,38,37,36},                    // chromatic down
                        {36,38,39,41,43,44,46,48},                    // major up
                        {48,46,44,43,41,39,38,36},                    // major down
                        {36,38,40,43,45,48,50,52},                    // natural minor up
                        {52,50,48,45,43,40,38,36},                    // natural minor down
                        {36,39,41,42,43,46,48,51},                    // major pentatonic up
                        {51,48,46,43,42,41,39,36},                    // major pentatonic down
                        {36,39,41,42,43,46,48,51},                    // blues up
                        {51,48,46,43,42,41,39,36},                    // blues down
                        {36,40,34,45,48,39,38,46},                    // random one
                        {36,38,35,45,37,43,50,41},                    // random two
                        {36,46,44,37,48,35,42,45},                    // random three
                        {36,44,48,38,39,43,44,38}};                   // random four               
                        
// global control params
int OSC_ONE_OFFSET = 12;      // amount to offset the original midi note (12 is one octave)
int OSC_TWO_OFFSET = 2;       // amount to offset the second midi note from the osc one's midi note (5 is a fifth, two is a second, etc)
int ARP_RATE_MIN = 32;        // minimum arpeggiator rate (in millisecs)
int ARP_RATE_MAX = 1024;      // maximum arpeggiator rate (in millisecs)
int LEGATO_MIN = 32;          // minimum note length (capping at 32 to avoid rollover artifacts)
int LEGATO_MAX = 1024;        // maximum note length 
int LPF_CUTOFF_MIN = 10;      // low pass filter min cutoff frequency
int LPF_CUTOFF_MAX = 245;     // low pass filter max cutoff frequency

void setup(){
  //initialize buttons
  pinMode(upButtonPin, INPUT);
  pinMode(downButtonPin, INPUT);
  pinMode(ledPin,OUTPUT);

  //initialize Mozzi objects
  startMozzi(CONTROL_RATE);
  oscOne.setFreq(48);
  oscTwo.setFreq(51);
  lpf.setResonance(128u);
  kGainChangeDelay.set(1000);
  //Serial.begin(9600); //Serial output can cause audio artifacting so this is commented out by default. Can be uncommented for debugging purposes.
}

void updateControl(){

  // read sequence selector buttons and debounce them
  // using mozziMicros() instead of millis() for timing calls because Mozzi disables millis() timer
  int upreading = digitalRead(upButtonPin);
  int downreading = digitalRead(downButtonPin);
  if (upreading != lastUpState){
    lastUpState = mozziMicros(); 
  }
  if ((mozziMicros() - lastUpDebounceTime) > debounceDelay){
    if (upreading != upButtonState){
      lastUpDebounceTime = mozziMicros();
      upButtonState = upreading;
      if (upButtonState == HIGH){
        sequence += 1;
        if (sequence >= numSequences){
          // if we get to the end of the sequences we'll rollover back to the first sequence
          sequence = 0;
        }
      }
    }
  }
  if (downreading != lastDownState){
    lastDownState = mozziMicros();
  }
  if ((mozziMicros() - lastDownDebounceTime) > debounceDelay){
    if (downreading != downButtonState){
      lastDownDebounceTime = mozziMicros();
      downButtonState = downreading;
      if (downButtonState == HIGH){
        sequence -= 1;
        if (sequence < 0){
          // if we get to the first sequence we'll rollover to the last sequence
          sequence = numSequences - 1;
        }
      }
    }
  }
  
  // read pot values
  int oscOne_val = mozziAnalogRead(OSC_ONE_PIN);
  int oscTwo_val = mozziAnalogRead(OSC_TWO_PIN);
  int modA_val = mozziAnalogRead(MODA_PIN);
  int modB_val = mozziAnalogRead(MODB_PIN);
  int modC_val = mozziAnalogRead(MODC_PIN);

  // map pot vals
  // These formulas set the range of values coming from the pots to value ranges that work well with the various control functionality.
  // You'll probably only need to mess with these if you want to expand or offset the ranges to suit your own project's needs
  int oscOne_offset = (OSC_ONE_OFFSET*2) * ((oscOne_val * DIV1023)-0.5);                  // offset of original midi note number +/- 1 octave
  float oscTwo_offset = ((oscTwo_val * DIV1023) * DIV10) * OSC_TWO_OFFSET;                // frequency offset for second oscillator +/- 0.2 oscOne freq
  float modA_freq = ARP_RATE_MIN + (ARP_RATE_MAX * (1-(modA_val * DIV1023)));             // arpeggiator rate from 32 millisecs to ~= 1 sec
  float modB_freq = 1-(modB_val * DIV1023);                                               // legato from 32 millisecs to full on (1 sec)
  int modC_freq = LPF_CUTOFF_MIN + (LPF_CUTOFF_MAX *(modC_val * DIV1023));                // lo pass filter cutoff freq ~=100Hz-8k

  // using an EventDelay to cycle through the sequence and play each note
  kGainChangeDelay.set(modA_freq);                                        // set the delay frequency                                           
  if(kGainChangeDelay.ready()){                                           
      if(gain==0){                                                        // we'll make changes to the oscillator freq when the note is off
        if(note >= numNotes){                                             // if we've reached the end of the sequence, loop back to the beginning
            note = 0;
        }
        // turn on the LED on first note of sequence only
        if (note==0){
          digitalWrite(ledPin,HIGH);  
        }else{
          digitalWrite(ledPin,LOW);
        }
        // set oscillator notes based on current note in sequence
        float noteOne = mtof(NOTES[sequence][note] + oscOne_offset);      // osc one's freq = note plus any offset from user
        float noteTwo = noteOne + oscTwo_offset;                          // osc two's freq = osc one's freq plus user offset
        oscOne.setFreq(noteOne);                                          
        oscTwo.setFreq(noteTwo);
        note += 1;
        
        // setting length of note
        gain = 1;
        kGainChangeDelay.set(modA_freq*(1-modB_freq));                    // set length that note is on based on user legato settings
      }
      else{
          gain = 0;
          kGainChangeDelay.set(modA_freq*modB_freq);                      // set length that note is off based on user legato settings
      }
    kGainChangeDelay.start();                                             // execute the delay specified above
  }
  // setting lo pass cutoff freq
  lpf.setCutoffFreq(modC_freq);                                           // set the lo pass filter cutoff freq per user settings
  
}

int updateAudio(){
  // calculating the output audio signal as follows:
  // 1. Summing the waveforms from the two oscillators
  // 2. Shifting their bits by 1 to keep them in a valid output range
  // 3. Multiplying the combined waveform by the gain (volume)
  // 4. Passing the signal through the low pass filter
  return (char)lpf.next((((oscOne.next() + oscTwo.next())>>2) * gain));
}

void loop(){
  audioHook();
}


Esquemáticos

Circuito en Tinkercad
Esquemático
  • Nota: El circuito es mostrado con una conexión a un parlante por la falta de una jack de audio para producir un prototipo en Tinkercad. En la versión oficial se usa un conector jack de audio hembra de 6,5mm, el cual se ubica de la misma manera dentro del circuito.

Resultado final

IyP2023CE3K.jpg IyP2023CE3Ktrasera.jpg

Referencias

  1. "Arpeggiating Synthetizer Mosquito I" Proyecto original de Instructables
  2. Página web oficial de la librería Mozzi
  3. Documentación Mosquito I