DECEMBER 15, 2023

<aside> 📯 This project is an Audio Player Replica Music Machine that combines hardware and software components to create an interactive music-making system. The system allows users to control music playback, volume, and song selection through physical controls while providing a visual representation of the audio with an equalizer-like display.

</aside>

🎵  Mode and Sound Generation

The code is designed for pre-recorded music. It plays music by triggering sampled audio files (MP3 format)

🌈  Visuals

The code includes a visualizer that displays a series of bars that respond to the amplitude of the audio being played. These bars create an "equalizer-like" effect that visually represents the music.

🎛️  Interface

2023-12-27 20-52-10.mp4

const int joystickLPin = 14; // X-axis of the joystick
const int joystickUDPin = 32; // Y-axis of the joystick
const int buttonPin = 21;     // Button pin

void setup() {
  Serial.begin(9600);
  pinMode(joystickLPin, INPUT);
  pinMode(joystickUDPin, INPUT);
  pinMode(buttonPin, INPUT_PULLUP);
}

void loop() {
  int joyX = analogRead(joystickLPin);
  int joyY = analogRead(joystickUDPin);
  bool buttonState = digitalRead(buttonPin);

  Serial.print(joyX);
  Serial.print(",");
  Serial.print(joyY);
  Serial.print(",");
  Serial.println(buttonState);

  delay(100);
}

import processing.serial.*;
import processing.sound.*;

Serial myPort;
String val;
int joyX, joyY, prevJoyX;
final int defaultJoyX = 2300;  // Fixed default X position
final int defaultJoyY = 2300;  // Fixed default Y position
final int joyThreshold = 350; // Threshold for joystick movement
SoundFile[] melodies;
int currentMelodyIndex = 0;
float volumeLevel = 0.3;
final int songAdvanceSeconds = 5;  // Time to jump forward in the song
boolean buttonState, prevButtonState = false;
String[] songNames = {"Für Elise", "Lakmé Act 1: Dôme Épais"};
Amplitude analyzer; // To analyze sound amplitude

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

  // Initialize serial communication
  try {
    myPort = new Serial(this, "/dev/tty.SLAB_USBtoUART", 9600);
    myPort.bufferUntil('\\n');
  }
  catch (Exception e) {
    println("Error opening serial port.");
    e.printStackTrace();
  }

  // Initialize the melodies array with the correct size
  melodies = new SoundFile[2];

  // Load melodies
  melodies[0] = new SoundFile(this, "fur_elise.mp3");
  melodies[1] = new SoundFile(this, "lakme_act1_dome_epais.mp3");

  // Initialize the analyzer
  analyzer = new Amplitude(this);

  // Start playing the first melody
  playMelody(currentMelodyIndex);
}

void draw() {
  background(0);

  if (myPort.available() > 0) {
    val = myPort.readStringUntil('\\n');
    if (val != null) {
      processInput(val);
    }
  }
  drawMediaPlayer();
  drawVisualizer();
}

void processInput(String input) {
  String[] vals = split(trim(input), ',');
  if (vals.length == 3) {
    joyX = int(vals[0]);
    joyY = int(vals[1]);
    buttonState = vals[2].equals("1");

    processJoystick();
    processButton();
  }
}

void processJoystick() {
  SoundFile currentMelody = melodies[currentMelodyIndex];

  // Restart the song if joystick moved fully left
  if (joyX < defaultJoyX - joyThreshold) {
    currentMelody.cue(0);
    currentMelody.play();
  }

  // Advance the song by a fixed time increment if joystick moved right
  if (joyX > prevJoyX + 30 && joyX > defaultJoyX + joyThreshold) {
    float newTime = currentMelody.position() + songAdvanceSeconds;
    if (newTime < currentMelody.duration()) {
      currentMelody.cue(newTime);
      currentMelody.play();
    }
  }
  prevJoyX = joyX;

  // Adjust volume based on joystick up/down movement
  if (abs(joyY - defaultJoyY) > joyThreshold) {
    volumeLevel = map(joyY, 0, 4095, 0, 1);
    volumeLevel = constrain(volumeLevel, 0, 1);
    currentMelody.amp(volumeLevel);
  }
}

void processButton() {
  if (buttonState && !prevButtonState) {
    currentMelodyIndex = (currentMelodyIndex + 1) % melodies.length;
    playMelody(currentMelodyIndex);
  }
  prevButtonState = buttonState;
}

void playMelody(int index) {
  for (int i = 0; i < melodies.length; i++) {
    if (i == index) {
      melodies[i].loop();
      melodies[i].amp(volumeLevel);
      analyzer.input(melodies[i]); // Change input for analyzer
    } else {
      melodies[i].stop();
    }
  }
}

void drawMediaPlayer() {
  SoundFile playingMelody = melodies[currentMelodyIndex];
  float progress = map(playingMelody.position(), 0, playingMelody.duration(), 0, width);
  fill(50, 50, 200);
  rect(0, height - 50, progress, 30);

  fill(200, 50, 50);
  rect(width - 60, height - 100, 30, map(volumeLevel, 0, 1, 0, -50));

  fill(255);
  text("Song: " + songNames[currentMelodyIndex], 10, height - 90);
  text("Position: " + playingMelody.position(), 10, height - 70);
  text("Volume: " + volumeLevel, width - 140, height - 70);
}

void drawVisualizer() {
  float amplitude = analyzer.analyze();
  int bars = 50; // Number of bars in the visualizer
  for (int i = 0; i < bars; i++) {
    float x = map(i, 0, bars, 0, width);
    float h = amplitude * 500; // Scaling amplitude
    fill(255, 100, 100);
    rect(x, height / 2 - h / 2, width / bars - 2, h);
  }
}

void stop() {
  for (SoundFile melody : melodies) {
    melody.stop();
  }
  myPort.stop();
  super.stop();
}