This is the project for ECE-GY 6183 DSP Lab.
Generates a random music clip under specific rules.
Opens a generated music clip randomly or a local .wav file, displays the circulated plot in the visualizer.
This project is written in Python 3.10.
Libraries used: Librosa, MIDIUtil, Mido,
NumPy, PyDub, PyGame,
tkinter.
Could be installed using pip
or homebrew
.
Simply run main.py
.
A standard MIDI file will be generated and then converted to a wav file in the music_clips folder. The filename is output_{date}.mid.
A dialog window would appear, asking the user to Open a wav file or Play a random clip.
Close the dialog window, the music visualizer would appear and play the previously chosen wav file or randomly picked music clip.
The music visualizer looks like the following, the figure in the PyGame window would change simultaneously with the output of the signal.
config/notes.json is the JSON file that defines the attributes of the music note that we later generate in the note_generator.py.
degrees: the octave that the music clip is based on.
MIDI notes for the C5 octave are 84(C5), 74(D4), 76(E4), 77(F4), 79(G4), 81(A4), 83(B4)
chords: note sequences that form chords for the music clip.
The Major C chord has Major C, E, and G music notes.
We used two Major C chords, one is C4(72), E3(64), G3(67), the other is C4(72), E4(76), G4(79). Notes in each array would be played simultaneously.
tones: the rhythm pattern.
Rhythm combines strong beats and weak beats.
Strong beats include the first beat of each measure (the downbeat) and other heavily accented beats.
The music combines strong beats and weak beats to create rhythmic patterns.
The numbers are in beats (quarter notes) and 0.25, 0.5, and 1 represent quarter notes, half notes, and whole notes, respectively.
upper: the upper bound of the interval of music notes
lower: the lower bound of the interval of music notes
beats: the beats per minute (BPM), indicating the number of beats in one minute. Define the BPM for strong, moderate, and weak beats.
Parameters (duration, tempo, and volume) for the music clip.
note_generator.py
generates a list of notes randomly according to the parameters given in the config/notes.json
.
It creates a class Note
. The Note
object has three attributes: octave
, lower
and upper
.
The octave attribute is the octave for this note. The three attributes match the degrees, lower and upper fields in the config/notes.json, respectively.
The Note
class has two functions, one for generating a single note and appending it to the list, and one for initializing a new note.
music_generator.py
generates a music clip, using the Note
class from the note_generator.py
.
It creates the class MusicClip
with a number of functions.
The create_sequence()
function adds the attributes of the pitch and the duration of each note inside a list.
The initialize_single_track()
function assigns the track name, adds a tempo, and a program change event.
The create_basic_track()
function generates a track with strong, moderate, and weak beats to form a rhythm.
The create_chord_track()
function generates a chord.
The create_midi_file()
function calls the create_basic_track()
and create_chord_track()
functions and adds these two tracks
and writes the MIDI file that contains the music clip.
converter.py
converts a MIDI file to a wav file according to the MIDI tuning standard.
After the data format is changed, the output wav file is written to the folder music_clips with a suffix of the date the wav file was created.
player_dialog.py
creates a dialog user interface using tkinter.
It has two buttons on the GUI window, one for the user to choose to open a wav file locally or play a random music clip that our project has generated. The root of the window is initialized in the main.py
.
The event listener of opening a local wav file button limits the file type to wav files only and uses functions provided by the filedialog from tkinter to open this file.
visualizer_components.py
creates classes of MusicAnalyzer
, BasicBar
, SimpleBar
, RotatedBar
, and Rectangle
.
BasicBar
creates a basic bar. Each bar represents a frequency, and the height represents the amplitude of the frequency.
SimpleBar
groups a number of BasicBar
objects together. Given a small time difference, we could get the new decibel after the small time difference and update the shape of the bar.
RotatedBar
inherits the attributes of SimpleBar
, and is built around a circle, with angles specified. Thus, we need the bars to rotate for an angle so they can root at the circle.
Rectangle
is the shape of the RotatedBar
, and the width and height are updated to match the audio signal. The rotation of the rectangle is also updated.
MusicAnalyzer
analyzes the piece of audio with the library librosa by generating a decibel-based spectrogram so that the decibel of a specific timestamp and frequency could be retrieved.
music_visualizer.py
uses PyGame to generate the music visualizer window.
It takes the MusicAnalyzer
class from visualizer_components.py
for audio signal processing.
It contains parameters for setting up the window and creates custom frequency patterns for the bars as a technique to show the frequency bars differently.
main.py
uses the class objects and functions from the above python files.
It first creates a random music clip, then opens the music player dialog for the user to choose a local wav file or play a music clip randomly from the music_clips folder. After the dialog window is closed, the music visualizer window would appear, displaying real-time signals and playing the music simultaneously.