How to Contribute
Writing New Analysis Functions for I-MaT: A Comprehensive Tutorial
Introduction
In this tutorial, I will provide you with a comprehensive guide on how to write new analysis functions that can be incorporated into I-MaT, an easy-to-use tool for analyzing musical pieces. This guide assumes no prior knowledge of programming. By the end of this tutorial, you will be able to write your own analysis functions, incorporate them into I-MaT, and select them from a CLI (Command Line Interface) menu.
An Overview of an Analysis Function
Let’s start by understanding what an analysis function is. In simple terms, an analysis function is a piece of code that takes some input data (in our case, a music object), performs some operations on this data, and returns a result (often in the form of a pandas DataFrame).
The main parts of a function include:
Function name: This is what we use to call (or “invoke”) the function.
Parameters: These are the inputs to the function. We pass these when we call the function.
Body: This is where the main operation of the function is performed.
Return value: This is the result that the function produces.
In I-MaT, an analysis function generally takes two parameters: a music_obj (a music21.stream.Stream object) and an identifier (a string used for labeling the analysis result), and it returns a pandas DataFrame.
Example Function
def calculate_total_duration(music_obj, identifier):
"""
Calculate the total duration of a music21 stream object.
Parameters
----------
music_obj : music21.stream.Stream
A music21 stream object representing a musical piece to be analyzed.
identifier : str
A string used to label the analysis result.
Returns
-------
pd.DataFrame
A pandas DataFrame that contains the total duration of the musical piece.
Notes
-----
This function calculates the sum of the durations of all notes in the piece. Rests are not included in this calculation.
"""
try:
# Initialize total_duration to 0
total_duration = 0
# Iterate over all notes in the music object
for note in music_obj.recurse().notes:
# Add the duration of the current note to total_duration
total_duration += note.duration.quarterLength
# Create a new pandas DataFrame for the results
df = pd.DataFrame({'Identifier': [identifier], 'Total Duration': [total_duration]})
# Return the results
return df
except Exception as e:
handle_error(e) # This is a function to handle any errors that might occur.
This function ‘calculate_total_duration’ takes as input a ‘music21’ stream object and an identifier string. It calculates the total duration of the music piece by summing the durations of all notes, and returns the results in a pandas DataFrame.
The ‘recurse().notes’ method is used to iterate over all notes in the music object, and the ‘duration.quarterLength’ attribute of each note object is used to get its duration.
Creating an Empty Frame for a New Analysis Function
Here’s an example of how to create an empty frame for a new analysis function:
def my_new_analysis_function(music_obj, identifier):
"""
Write a brief description of your function here.
Parameters
----------
music_obj : music21.stream.Stream
A music21 stream object representing a musical piece to be analyzed.
identifier : str
A string used to label the analysis result.
Returns
-------
pd.DataFrame
A pandas DataFrame that contains the results of the analysis.
Notes
-----
Write any additional notes about your function here.
"""
try:
# Write the main code of your function here
# Create a new pandas DataFrame for your results
df = pd.DataFrame({'Identifier': [identifier], 'Result Name': [Result Values]})
# Return the results
return df
except Exception as e:
handle_error(e) # This is a function to handle any errors that might occur.
When you’re ready to implement your function, replace the comment ‘# Write the main code of your function here’ with your code.
But for now, let’s break down the function into its four seperate parts:
1) Defining the function:
def my_new_analysis_function(music_obj, identifier):
The ‘def’ keyword is used to define a function in Python. ‘my_new_analysis_function is the name of the function that we’re creating. ‘music_obj’ and ‘identifier’ are the inputs or parameters of this function. The colon ‘:’ marks the start of the function body.
2) Documenting the function:
"""
Write a brief description of your function here.
Parameters
----------
music_obj : music21.stream.Stream
A music21 stream object representing a musical piece to be analyzed.
identifier : str
A string used to label the analysis result.
Returns
-------
pd.DataFrame
A pandas DataFrame that contains the results of the analysis.
Notes
-----
Write any additional notes about your function here.
"""
This part is the documentation (or “docstring”) of the function. It’s a good practice to document your functions, as it helps other people (and your future self) understand what the function does. The documentation includes a description of the function, the parameters, the return value, and any additional notes.
3) The main body of the function:
try:
# Write the main code of your function here
# Create a new pandas DataFrame for your results
df = pd.DataFrame({'Identifier': [identifier], 'Result Name': [Result Values]})
# Return the results
return df
The ‘try’ statement allows you to handle errors gracefully. The code that you want to “try” running goes inside this block.
The ‘# Write the main code of your function here’ comment is a placeholder for where you’ll add your own code. The details of this will depend on what you want your function to do.
‘df = pd.DataFrame({‘Identifier’: [identifier], ‘Result Name’: [Result Values]})’ creates a new pandas DataFrame. This is where you’ll store the results of your analysis.
The ‘return df’ statement specifies that your function should return the DataFrame ‘df’. This is the result that your function produces.
4) Error handling:
except Exception as e:
handle_error(e) # This is a function to handle any errors that might occur.
The ‘except’ statement specifies what should happen if an error occurs in the ‘try’ block. In this case, we’re saying that if any kind of ‘Exception’ (error) occurs, it should be handled by the ‘handle_error(e)’ function. This is a function that you would define elsewhere in your code to specify how to handle different types of errors.
Incorporating Your Analysis Function into I-MaT
Once you’ve written your analysis function, you’ll need to incorporate it into I-MaT so that it can be used as part of the analysis workflow. Here’s how to do this:
Add your function to the relevant Python module where all the other analysis functions are defined. All the analysis functions are defined in the analysis.functions.py module.
Add an menu entry for your analysis function within the relevant menu. All the menu entries are defined in the iMaT.src.cli.menu_entries.py module.