{
"cells": [
{
"cell_type": "markdown",
"id": "blessed-heavy",
"metadata": {
"colab_type": "text",
"id": "view-in-github"
},
"source": [
"\n",
" \n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "legitimate-immigration",
"metadata": {},
"source": [
"# An Introduction to Symbolic Music Processing with Partitura\n",
"\n",
"Partitura is python 3 package for symbolic music processing developed and maintained at CP JKU Linz (and other contributors). It's intended to give a lightweight musical part representation that makes many score properties easily accessible for a variety of tasks. Furthermore, it's a very useful I/O utility to parse computer formats of symbolic music."
]
},
{
"cell_type": "markdown",
"id": "nonprofit-communication",
"metadata": {
"id": "3tvQmcSB7rrL"
},
"source": [
"## 1. Install and import\n",
"\n",
"Partitura is available in github https://github.com/CPJKU/partitura\n",
"\n",
"You can install it with `pip install partitura`.\n",
"\n",
"However if you are interested in features that still have to be officially released, it's better to install the develop branch."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "facial-quarterly",
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "PeabdL1k7YC4",
"outputId": "fcb7d1be-27a1-4c79-c5d3-8cbfa54cae44",
"scrolled": true
},
"outputs": [],
"source": [
"try:\n",
" import google.colab\n",
" IN_COLAB = True\n",
"except:\n",
" IN_COLAB = False\n",
"\n",
"if IN_COLAB:\n",
" # Install partitura\n",
" # Issues on Colab with newer versions of MIDO\n",
" # this install should be removed after the following\n",
" # pull request is accepted in MIDO\n",
" # https://github.com/mido/mido/pull/584\n",
" ! pip install mido==1.2.10\n",
" ! pip install partitura\n",
"else:\n",
" pass"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "impressed-principle",
"metadata": {},
"outputs": [],
"source": [
"import glob\n",
"import os\n",
"import partitura as pt\n",
"import numpy as np\n",
"import matplotlib.pyplot as plt\n",
"import os\n",
"os.environ['KMP_DUPLICATE_LIB_OK']='True'\n",
"import IPython.display as ipd"
]
},
{
"cell_type": "markdown",
"id": "toxic-italian",
"metadata": {
"id": "CX8wCxyK7emp"
},
"source": [
"#### Dataset for this tutorial\n",
"\n",
"In this tutorial we are going to use the [Vienna 4x22 Corpus](https://repo.mdw.ac.at/projects/IWK/the_vienna_4x22_piano_corpus/index.html) which consists of performances of 4 classical piano pieces, which have been aligned to their corresponding scores.\n",
"\n",
"The dataset contains:\n",
"\n",
"* Scores in MusicXML format (4 scores)\n",
"* Performances in MIDI files (88 in total, 22 performances per piece, each by a different pianist)\n",
"* Score to performance alignments in Match file format (88 in total one file per performance)"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "photographic-profession",
"metadata": {},
"outputs": [
{
"data": {
"application/vnd.jupyter.widget-view+json": {
"model_id": "84e109e3122046c5b376252571d5d961",
"version_major": 2,
"version_minor": 0
},
"text/plain": [
"Output()"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# setup the dataset\n",
"if IN_COLAB:\n",
" !git clone https://github.com/CPJKU/vienna4x22.git\n",
" DATASET_DIR = \"./vienna4x22\"\n",
" MUSICXML_DIR = os.path.join(DATASET_DIR, 'musicxml')\n",
" MIDI_DIR = os.path.join(DATASET_DIR, 'midi')\n",
" MATCH_DIR = os.path.join(DATASET_DIR, 'match')\n",
"else:\n",
" import sys, os\n",
" sys.path.append(os.path.join(os.path.dirname(os.path.dirname(os.getcwd())), \"utils\"))\n",
" from load_data import init_dataset\n",
" DATASET_DIR = init_dataset()\n",
" MUSICXML_DIR = os.path.join(DATASET_DIR, 'musicxml')\n",
" MIDI_DIR = os.path.join(DATASET_DIR, 'midi')\n",
" MATCH_DIR = os.path.join(DATASET_DIR, 'match')"
]
},
{
"cell_type": "markdown",
"id": "valued-helena",
"metadata": {},
"source": [
"## 2. Loading and Exporting Files\n",
"\n",
"One of the main use cases of partitura is to load and export common symbolic music formats."
]
},
{
"cell_type": "markdown",
"id": "sonic-better",
"metadata": {},
"source": [
"### Supported Formats\n",
"\n",
"#### Reading\n",
"\n",
"##### Symbolic Scores\n",
"\n",
"These methods return `Score` objects.\n",
"\n",
"| Format | Method| Notes |\n",
"|:--------------------|:---|:--------------------------------------------------------------------------------------------------------------------------------|\n",
"| Any Symbolic Format | `partitura.load_score` | (MIDI, MusicXML, MIDI, MEI, Kern) |\n",
"| MusicXML | `partitura.load_musicxml`| |\n",
"| MIDI | `partitura.load_score_midi`| Pitch spelling, key signature (optional) and other information is inferred with methods in `partitura.musicanalysis`.\n",
"| MEI | `partitura.load_mei`|\n",
"| Humdrum Kern | `partitura.load_kern`|\n",
"| MuseScore |`partitura.load_via_musescore`| Requires [MuseScore](https://musescore.org/en). Loads all formats supported by MuseScore. Support on Windows is still untested.\n",
"\n",
"##### Symbolic Performances\n",
"\n",
"These methods return a `Performance`.\n",
"\n",
"|Format| Method|Notes|\n",
"|:---|:---|:---|\n",
"|MIDI|`partitura.load_performance_midi`| Loads MIDI file as a performance, including track, channel and program information. Time signature and tempo information are only used to compute the time of the MIDI messages in seconds. Key signature information is ignored\n",
"\n",
"##### Alignments\n",
"\n",
"These methods return score-to-performance alignments (discussed in another notebook).\n",
"\n",
"|Format| Method|Notes|\n",
"|:---|:---|:---|\n",
"|Match file| `partitura.load_match`| Returns alignment, a performance as `PerformedPart` and optionally a `Part`. See usage below.\n",
"|Nakamura et al. corresp file | `partitura.load_nakamuracorresp`|\n",
"|Nakamura et al. match file| `partitura.load_nakamuramatch`|\n",
"\n",
"#### Writing\n",
"\n",
"##### Symbolic Scores\n",
"\n",
"|Format| Method|Notes|\n",
"|:---|:---|:---|\n",
"|MusicXML| `partitura.save_musicxml`|\n",
"|MIDI| `partitura.save_score_midi`| Includes Key signature, time signature and tempo information.\n",
"\n",
"##### Symbolic Performances\n",
"|Format| Method|Notes|\n",
"|:---|:---|:---|\n",
"|MIDI|`partitura.save_performance_midi`| Does not include key signature or time signature information\n",
"\n",
"##### Alignments\n",
"\n",
"|Format| Method|Notes|\n",
"|:---|:---|:---|\n",
"|Match file| `partitura.save_match`| \n"
]
},
{
"cell_type": "markdown",
"id": "dd98b602",
"metadata": {},
"source": [
"## 3. Internal Representations\n",
"\n",
"### 3.0 The Score Object\n",
"\n",
"Score loading functions return a ```Score``` object. It's a wrapper storing score metadata such as composer and piece as well as all musical material in the score. At the same time it acts as an iterator of contained ```Part``` objects.\n",
"\n",
"### 3.1 The Part Object\n",
"\n",
"The ```Part``` object is the central object of partitura. Its name stems from musicxml parts and usually a single instrument.\n",
"- it is a timeline object\n",
"- time is measured in divs\n",
"- its elements are timed objects, i.e. they have a starting time and an ending time\n",
"- external score files are loaded into a part\n",
"- parts can be exported into score files\n",
"- it contains many useful methods related to its properties\n",
"\n",
"Here's a visual representation of the ```Part``` object representing the first measure of Chopin's Nocturne Op. 9 No. 2"
]
},
{
"cell_type": "markdown",
"id": "5754b952",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "c9179e78",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Part id=\"P1\" name=\"Piano\"\n",
" │\n",
" ├─ TimePoint t=0 quarter=12\n",
" │ │\n",
" │ └─ starting objects\n",
" │ │\n",
" │ ├─ 0--48 Measure number=1 name=1\n",
" │ ├─ 0--48 Note id=n01 voice=1 staff=2 type=whole pitch=A4\n",
" │ ├─ 0--48 Page number=1\n",
" │ ├─ 0--24 Rest id=r01 voice=2 staff=1 type=half\n",
" │ ├─ 0--48 System number=1\n",
" │ └─ 0-- TimeSignature 4/4\n",
" │\n",
" ├─ TimePoint t=24 quarter=12\n",
" │ │\n",
" │ ├─ ending objects\n",
" │ │ │\n",
" │ │ └─ 0--24 Rest id=r01 voice=2 staff=1 type=half\n",
" │ │\n",
" │ └─ starting objects\n",
" │ │\n",
" │ ├─ 24--48 Note id=n02 voice=2 staff=1 type=half pitch=C5\n",
" │ └─ 24--48 Note id=n03 voice=2 staff=1 type=half pitch=E5\n",
" │\n",
" └─ TimePoint t=48 quarter=12\n",
" │\n",
" └─ ending objects\n",
" │\n",
" ├─ 0--48 Measure number=1 name=1\n",
" ├─ 0--48 Note id=n01 voice=1 staff=2 type=whole pitch=A4\n",
" ├─ 24--48 Note id=n02 voice=2 staff=1 type=half pitch=C5\n",
" ├─ 24--48 Note id=n03 voice=2 staff=1 type=half pitch=E5\n",
" ├─ 0--48 Page number=1\n",
" └─ 0--48 System number=1\n"
]
}
],
"source": [
"path_to_musicxml = pt.EXAMPLE_MUSICXML\n",
"part = pt.load_musicxml(path_to_musicxml)[0] # we access the first (and sole) part of this score by indexing the returned score\n",
"print(part.pretty())"
]
},
{
"cell_type": "markdown",
"id": "874a18d5",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "markdown",
"id": "c5bae1ed",
"metadata": {},
"source": [
"### 3.2 Notes\n",
"\n",
"Each ```Part``` object contains a list notes. Notes inherit from the ```TimedObject``` class. Like all ```TimedObjects``` they contain a (possibly coincident) start time and end time, encoded as ```TimePoint``` objects."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "423aac6a",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[,\n",
" ,\n",
" ]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"part.notes"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "0a929369",
"metadata": {},
"outputs": [],
"source": [
"# uncomment to print all note object attributes\n",
"# dir(part.notes[0])"
]
},
{
"cell_type": "markdown",
"id": "c2287849",
"metadata": {},
"source": [
"You can create notes (without timing information) and then add it to a part by specifying start and end times (in divs!). Use each note object only once! You can remove notes from a part."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "2a8293c9",
"metadata": {},
"outputs": [],
"source": [
"a_new_note = pt.score.Note(id='n04', step='A', octave=4, voice=1)\n",
"part.add(a_new_note, start=3, end=15)\n",
"# print(part.pretty())"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "eba2fa93",
"metadata": {},
"outputs": [],
"source": [
"part.remove(a_new_note)\n",
"# print(part.pretty())"
]
},
{
"cell_type": "markdown",
"id": "a8649483",
"metadata": {},
"source": [
"### 3.3 Converting from divs to musical units and back\n",
"\n",
"Integer divs are useful for encoding scores but unwieldy for human readers. Partitura offers a variety of ```*unit*_maps``` from the timeline unit \"div\" to musical units such as \"beats\" (in two different readings) or \"quarters\". For the inverse operation the corresponding ```inv_*unit*_map``` exist as well. Quarter to div ratio is a fixed value for a ```Part``` object, but units like beats might change with time signature, so these ```maps``` are implemented as ```Part``` methods.\n",
"\n",
"Let's look at how to get the ending position in beats of the last note in our example ```Part```."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "e95eb0f7",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array(4.)"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"part.beat_map(part.notes[0].end.t)"
]
},
{
"cell_type": "markdown",
"id": "7f079345",
"metadata": {},
"source": [
"### Timeline maps\n",
"\n",
"For several TimedObjects partitura offers convenient ```maps``` that return the object at a position, usually given in divs. Other musical information such as key and time signature is valid for a segment of the score but only encoded in one location. ```Maps``` also retrieve the \"currently active\" time or key signature at any score position.\n",
"\n",
"|Returns| Method|Notes|\n",
"|:---|:---|:---|\n",
"|Time Signature | `part.time_signature_map`|return a triple of beats, beat_type, and musical_beats|\n",
"|Key Signature | `part.key_signature_map`||\n",
"|Measure | `part.measure_map`| returns the start and end point of the current measure |\n",
"|Measure Number| `part.measure_number_map`||\n",
"|Metrical Position| `part.metrical_position_map`||\n",
"|Divs -> Beat| `part.beat_map`||\n",
"|Beat -> Divs| `part.inv_beat_map`||\n",
"|Divs -> Quarters| `part.quarter_map`||\n",
"|Quarter -> Divs| `part.inv_quarter_map`||\n",
"|Quarter Durations| `part.quarter_duration_map`||\n"
]
},
{
"cell_type": "markdown",
"id": "bf1d6ae9",
"metadata": {},
"source": [
"### 3.4 Iterating over arbitrary musical objects in a part\n",
"\n",
"Each ```Part``` object contains a central method ```iter_all``` to iterate over all instances of the ```TimedObject``` class or its subclasses of a part. The ```iter_all``` method returns an iterator and takes five optional parameters: \n",
"- A ```TimedObject``` subclass whose instances are returned. You can find them all in the partitura/partitura/score.py file. Default is all classes.\n",
"- A ```include_subclasses``` flag. If true, instances of subclasses are returned too. E.g. ``` part.iter_all(pt.score.TimedObject, include_subclasses=True)``` returns all objects or ```part.iter_all(pt.score.GenericNote, include_subclasses=True)``` returns all notes (grace notes, standard notes)\n",
"- A start time in divs to specify the search interval (default is beginning of the part)\n",
"- An end time in divs to specify the search interval (default is end of the part)\n",
"- A ```mode``` parameter to define whether to search for starting or ending objects, defaults to starting."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "74943a93",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0--48 Measure number=1 name=1\n"
]
}
],
"source": [
"for measure in part.iter_all(pt.score.Measure):\n",
" print(measure)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "6cbfd044",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"0--48 Note id=n01 voice=1 staff=2 type=whole pitch=A4\n",
"0--24 Rest id=r01 voice=2 staff=1 type=half\n"
]
}
],
"source": [
"for note in part.iter_all(pt.score.GenericNote, include_subclasses=True, start=0, end=24):\n",
" print(note)"
]
},
{
"cell_type": "markdown",
"id": "5ef0e97b",
"metadata": {},
"source": [
"### 3.5 TimedObject list retrieval\n",
"\n",
"For several TimedObjects partitura offers convenient retrieval functions that return the a list of that object without having to use the general `part.iter_all`.\n",
"\n",
"\n",
"|Returns| Method|Notes|\n",
"|:---|:---|:---|\n",
"|Notes | `part.notes`| |\n",
"|Tied Notes | `part.notes_tied`| Tied notes are merged into a single sounding note|\n",
"|Measures | `part.measures`||\n",
"|Rests| `part.rests`||\n",
"|Repeats| `part.repeats`||\n",
"|Key Signatures| `part.key_sigs`||\n",
"|Time Signatures| `part.time_sigs`||\n",
"|Dynamics| `part.dynamics`| Dynamics markings in the score|\n",
"|Articulations| `part.articulations`|Articulation markings in the score|"
]
},
{
"cell_type": "markdown",
"id": "d1455a5f",
"metadata": {},
"source": [
"### 3.6 Example: Adding a new measure and a note at its downbeat\n",
"\n",
"Let's use class retrieval, time mapping, and object creation together and add a new measure with a single beat-length note at its downbeat. This code works even if you know nothing about the underlying score."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "fe430921",
"metadata": {},
"outputs": [],
"source": [
"# figure out the last measure position, time signature and beat length in divs\n",
"measures = [m for m in part.iter_all(pt.score.Measure)]\n",
"last_measure_number = measures[-1].number\n",
"append_measure_start = measures[-1].end.t \n",
"Last_measure_ts = part.time_signature_map(append_measure_start)\n",
"\n",
"Last_measure_ts = part.time_signature_map(append_measure_start)\n",
"one_beat_in_divs_at_the_end = append_measure_start - part.inv_beat_map(part.beat_map(append_measure_start)-1)\n",
"append_measure_end = append_measure_start + one_beat_in_divs_at_the_end*Last_measure_ts[0]\n",
"\n",
"# add a measure\n",
"a_new_measure = pt.score.Measure(number = last_measure_number+1)\n",
"part.add(a_new_measure, start=append_measure_start, end=append_measure_end)\n",
"# add a note\n",
"a_new_note = pt.score.Note(id='n04', step='A', octave=4, voice=1)\n",
"part.add(a_new_note, start=append_measure_start, end=append_measure_start+one_beat_in_divs_at_the_end)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "f9d738a5",
"metadata": {},
"outputs": [],
"source": [
"# print(part.pretty())"
]
},
{
"cell_type": "markdown",
"id": "2bf8c482",
"metadata": {},
"source": [
"### 3.7 Example: Cats on Keyboards\n",
"\n",
"Now that we know the basics of partitura internals we use some randomization to create random scores, that are nevertheless fully specified and exportable to musicxml."
]
},
{
"cell_type": "markdown",
"id": "f332ca5c",
"metadata": {},
"source": [
""
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "d6eb12f2",
"metadata": {},
"outputs": [],
"source": [
"def addnote(midipitch, part, voice, start, end, idx):\n",
" \"\"\"\n",
" adds a single note by midipitch to a part\n",
" \"\"\"\n",
" step, alter, octave = pt.utils.music.midi_pitch_to_pitch_spelling(midipitch)\n",
"\n",
" part.add(pt.score.Note(id='n{}'.format(idx), step=step, \n",
" octave=int(octave), alter=alter, voice=voice, staff=int((voice-1)%2+1)),\n",
" start=start, end=end)"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "572e856c",
"metadata": {},
"outputs": [],
"source": [
"l = 100\n",
"p = pt.score.Part('cat_on_keyboard', 'Cat on Keyboard', quarter_duration=8)\n",
"dur = np.random.randint(1,20, size=(4,l))\n",
"ons = np.cumsum(np.concatenate((np.zeros((4,1)),dur), axis=1), axis = 1)\n",
"pitch = np.row_stack((np.random.randint(70,80, size=(1,l)),\n",
" np.random.randint(50,60, size=(1,l)),\n",
" np.random.randint(60,70, size=(1,l)),\n",
" np.random.randint(40,50, size=(1,l))\n",
" ))"
]
},
{
"cell_type": "code",
"execution_count": 17,
"id": "f9f03a50",
"metadata": {},
"outputs": [],
"source": [
"for k in range(l):\n",
" for j in range(4):\n",
" addnote(pitch[j,k], p, j+1, ons[j,k], ons[j,k]+dur[j,k], \"v\"+str(j)+\"n\"+str(k))"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "09fb6b45",
"metadata": {},
"outputs": [],
"source": [
"# sanitize the part\n",
"p.add(pt.score.TimeSignature(4, 4), start=0)\n",
"p.add(pt.score.Clef(1, \"G\", line = 3, octave_change=0),start=0)\n",
"p.add(pt.score.Clef(2, \"F\", line = 4, octave_change=0),start=0)\n",
"pt.score.add_measures(p)\n",
"pt.score.tie_notes(p)"
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "834582d5",
"metadata": {},
"outputs": [],
"source": [
"# pt.save_score_midi(p, \"CatPerformance.mid\", part_voice_assign_mode=2)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "006f02ed",
"metadata": {},
"outputs": [],
"source": [
"# pt.save_musicxml(p, \"CatScore.musicxml\")"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "d8f2fd97",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"pr = pt.utils.compute_pianoroll(p)\n",
"fig, ax = plt.subplots(1, figsize=(8, 2))\n",
"ax.imshow(pr.toarray()[35:85,:500], origin=\"lower\", cmap='gray', interpolation='nearest', aspect='auto')\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "5c266150",
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"text/html": [
"\n",
" \n",
" "
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"SAMPLE_RATE = 11025\n",
"\n",
"audio_p = pt.utils.synth.synthesize(note_info=p, samplerate=SAMPLE_RATE, harmonic_dist=10, bpm=180)\n",
"ipd.display(ipd.Audio(data=audio_p, rate=SAMPLE_RATE, normalize=False))"
]
},
{
"cell_type": "markdown",
"id": "643f1c34",
"metadata": {},
"source": [
"### 3.8 The Performance Object\n",
"\n",
"Performance loading functions return a ```Performance``` object. It's a wrapper storing performance metadata as well as all information of a MIDI file. At the same time it acts as an iterator of contained ```PerformedPart``` objects."
]
},
{
"cell_type": "markdown",
"id": "9fda0e98",
"metadata": {},
"source": [
"### 3.9 The PerformedPart Object\n",
"\n",
"The ```PerformedPart``` class is a wrapper for MIDI tracks. Its structure is much simpler than the ```Part``` object's:\n",
"- a notes property that consists of list of MIDI notes as dictionaries\n",
"- a controls property that consists of list of MIDI CC messages\n",
"- some more utility methods and properties, such as program changes, note array, and others."
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "e6508269",
"metadata": {},
"outputs": [],
"source": [
"path_to_midifile = pt.EXAMPLE_MIDI\n",
"# as for scores we index the performance to retrieve the first (and sole) performedpart\n",
"performedpart = pt.load_performance_midi(path_to_midifile)[0] "
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "d4421eeb",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[,\n",
" ,\n",
" ]"
]
},
"execution_count": 24,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"performedpart.notes"
]
},
{
"cell_type": "markdown",
"id": "identical-gathering",
"metadata": {},
"source": [
"## 4. Extracting Information from Scores and Performances\n",
"\n",
"For many MIR tasks we need to extract specific information out of scores or performances. \n",
"Two of the most common representations are **note arrays** and **piano rolls** (note that there is some overlap in the way that these terms are used in the literature).\n",
"\n",
"Partitura provides convenience methods to extract these common features in a few lines!\n",
"\n",
"To estimate even higher level information, partitura offers implementations of standard methods in automatic **music analysis**."
]
},
{
"cell_type": "markdown",
"id": "27076f7a",
"metadata": {},
"source": [
"### 4.1 Note Arrays\n",
"\n",
"A **note array** is a 2D array in which each row represents a note in the score/performance and each column represents different attributes of the note.\n",
"\n",
"In partitura, note arrays are [structured numpy arrays](https://numpy.org/devdocs/user/basics.rec.html), which are ndarrays in which each \"column\" has a name, and can be of different datatypes.\n",
"This allows us to hold information that can be represented as integers (MIDI pitch/velocity), floating point numbers (e.g., onset time) or strings (e.g., note ids).\n",
"\n",
"In this tutorial we are going to cover 3 main cases\n",
"\n",
"* Getting a note array from `Part` and `PerformedPart` objects\n",
"* Extra information and alternative ways to generate a note array\n",
"* Creating a custom note array from scratch from a `Part` object"
]
},
{
"cell_type": "markdown",
"id": "ordinary-psychology",
"metadata": {},
"source": [
"### 4.2 Getting a note array from `Part` or `Score` objects"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "first-basin",
"metadata": {},
"outputs": [],
"source": [
"# Path to the MusicXML file\n",
"score_fn = os.path.join(MUSICXML_DIR, 'Chopin_op38.musicxml')\n",
"\n",
"# Load the score into a `Score` object\n",
"score = pt.load_musicxml(score_fn)\n",
"\n",
"# Get note array. Calling note arrays from scores will merge the contained parts' note arrays into one.\n",
"score_note_array = score.note_array() "
]
},
{
"cell_type": "markdown",
"id": "looking-whole",
"metadata": {},
"source": [
"It is that easy!"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "alternate-coordinate",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[(-4., 1., -2. , 0.5, 0, 8, 60, 4, 'n2', 16)\n",
" (-4., 1., -2. , 0.5, 0, 8, 72, 1, 'n1', 16)\n",
" (-3., 2., -1.5, 1. , 8, 16, 60, 4, 'n4', 16)\n",
" (-3., 2., -1.5, 1. , 8, 16, 72, 1, 'n3', 16)\n",
" (-1., 1., -0.5, 0.5, 24, 8, 60, 4, 'n6', 16)\n",
" (-1., 1., -0.5, 0.5, 24, 8, 72, 1, 'n5', 16)\n",
" ( 0., 2., 0. , 1. , 32, 16, 60, 4, 'n8', 16)\n",
" ( 0., 2., 0. , 1. , 32, 16, 72, 1, 'n7', 16)\n",
" ( 2., 1., 1. , 0.5, 48, 8, 60, 4, 'n10', 16)\n",
" ( 2., 1., 1. , 0.5, 48, 8, 72, 1, 'n9', 16)]\n"
]
}
],
"source": [
"# Lets see the first notes in this note array\n",
"print(score_note_array[:10])"
]
},
{
"cell_type": "markdown",
"id": "toxic-publicity",
"metadata": {},
"source": [
"\n",
"\n",
"By default, Partitura includes some of the most common note-level information in the note array:"
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "subtle-millennium",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"('onset_beat', 'duration_beat', 'onset_quarter', 'duration_quarter', 'onset_div', 'duration_div', 'pitch', 'voice', 'id', 'divs_pq')\n"
]
}
],
"source": [
"print(score_note_array.dtype.names)"
]
},
{
"cell_type": "markdown",
"id": "exact-practice",
"metadata": {},
"source": [
"* `onset_beat` is the onset time in beats (as indicated by the time signature). In partitura, negative onset times in beats represent pickup measures. Onset time 0 is the start of the first measure.\n",
"* `duration_beat` is the duration of the note in beats\n",
"* `onset_quarter` is the onset time of the note in quarters (independent of the time signature). Similarly to onset time in beats, negative onset times in quarters represent pickup measures and onset time 0 is the start of the first measure.\n",
"* `duration_quarter`is the duration of the note in quarters\n",
"* `onset_div` is the onset of the note in *divs*, which is generally a number that allows to represent the note position and duration losslessly with integers. In contrast to onset time in beats or quarters, onset time in divs always start at 0 at the first \"element\" in the score (which might not necessarily be a note).\n",
"* `duration_div` is the duration of the note in divs.\n",
"* `pitch` is the MIDI pitch (MIDI note number) of the note\n",
"* `voice` is the voice of the note (in polyphonic music, where there can be multiple notes at the same time)\n",
"* `id` is the note id (as appears in MusicXML or MEI formats)"
]
},
{
"cell_type": "markdown",
"id": "compressed-baseball",
"metadata": {},
"source": [
"### 4.3 Getting a note array from `PerformedPart` or `Performance` objects"
]
},
{
"cell_type": "code",
"execution_count": 28,
"id": "passing-lending",
"metadata": {},
"outputs": [],
"source": [
"# Path to the MIDI file\n",
"performance_fn = os.path.join(MIDI_DIR, 'Chopin_op38_p01.mid')\n",
"\n",
"# Loading the file to a PerformedPart\n",
"performance = pt.load_performance_midi(performance_fn)\n",
"\n",
"# Get note array!\n",
"performance_note_array = performance.note_array()"
]
},
{
"cell_type": "markdown",
"id": "bright-equity",
"metadata": {},
"source": [
"Since performances contain have other information not included in scores, the default fields in the note array are a little bit different:"
]
},
{
"cell_type": "code",
"execution_count": 29,
"id": "pointed-stupid",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"('onset_sec', 'duration_sec', 'onset_tick', 'duration_tick', 'pitch', 'velocity', 'track', 'channel', 'id')\n"
]
}
],
"source": [
"print(performance_note_array.dtype.names)"
]
},
{
"cell_type": "markdown",
"id": "cathedral-generator",
"metadata": {},
"source": [
"* `onset_sec` is the onset time of the note in seconds. Onset time in seconds is always $\\geq 0$ (otherwise, the performance would violate the laws of physics ;)\n",
"* `duration_sec` is the duration of the note in seconds\n",
"* `onset_tick` is the onset time of the note in MIDI ticks. \n",
"* `duration_tick` is the duration of the note in MIDI ticks\n",
"* `pitch` is the MIDI pitch\n",
"* `velocity` is the MIDI velocity\n",
"* `track` is the track number in the MIDI file\n",
"* `channel` is the channel in the MIDI file\n",
"* `id` is the ID of the notes (automatically generated for MIDI file according to onset time)"
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "subject-reducing",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[(5.6075 , 0.12625, 44860, 1010, 72, 37, 0, 0, 'P00_n0')\n",
" (5.63375, 0.0975 , 45070, 780, 60, 27, 0, 0, 'P00_n1')\n",
" (6.07 , 0.21625, 48560, 1730, 72, 45, 0, 0, 'P00_n2')\n",
" (6.11125, 0.2525 , 48890, 2020, 60, 26, 0, 0, 'P00_n3')\n",
" (6.82625, 0.17 , 54610, 1360, 60, 39, 0, 0, 'P00_n4')]\n"
]
}
],
"source": [
"print(performance_note_array[:5])"
]
},
{
"cell_type": "markdown",
"id": "naval-prescription",
"metadata": {},
"source": [
"### 4.4 Example: Create a `PerformedPart` directly from a note array"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "spread-performer",
"metadata": {},
"outputs": [],
"source": [
"# Get a note_array with notes played on your computer keyboard:\n",
"# ----------> https://editor.p5js.org/oemei/full/6nux_mRgT\n",
"# replace the note array below with the generated code: \n",
"note_array = np.array(\n",
" [(60, 0, 2, 40),\n",
" (66, 2, 1, 80)],\n",
" dtype=[(\"pitch\", \"i4\"),\n",
" (\"onset_sec\", \"f4\"),\n",
" (\"duration_sec\", \"f4\"),\n",
" (\"velocity\", \"i4\"),\n",
" ]\n",
")\n",
"# Note array to `PerformedPart`\n",
"performed_part = pt.performance.PerformedPart.from_note_array(note_array)"
]
},
{
"cell_type": "markdown",
"id": "catholic-dealer",
"metadata": {},
"source": [
"We can then export the `PerformedPart` directly to a MIDI file!"
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "changed-check",
"metadata": {},
"outputs": [],
"source": [
"# export as MIDI file\n",
"# pt.save_performance_midi(performed_part, \"example.mid\")"
]
},
{
"cell_type": "markdown",
"id": "typical-taxation",
"metadata": {},
"source": [
"### 4.5 Extending note arrays with built-in flags\n",
"\n",
"Sometimes we require more information in a note array. The simplest solutions is to pass a number of argument flags for additional fields."
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "figured-coordinator",
"metadata": {},
"outputs": [],
"source": [
"extended_score_note_array = pt.utils.music.ensure_notearray(\n",
" score,\n",
" include_pitch_spelling=True, # adds 3 fields: step, alter, octave \n",
" include_key_signature=True, # adds 2 fields: ks_fifths, ks_mode\n",
" include_time_signature=True, # adds 2 fields: ts_beats, ts_beat_type \n",
" include_metrical_position=True, # adds 3 fields: is_downbeat, rel_onset_div, tot_measure_div\n",
" include_grace_notes=True # adds 2 fields: is_grace, grace_type\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 34,
"id": "vietnamese-pathology",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"('onset_beat',\n",
" 'duration_beat',\n",
" 'onset_quarter',\n",
" 'duration_quarter',\n",
" 'onset_div',\n",
" 'duration_div',\n",
" 'pitch',\n",
" 'voice',\n",
" 'id',\n",
" 'step',\n",
" 'alter',\n",
" 'octave',\n",
" 'is_grace',\n",
" 'grace_type',\n",
" 'ks_fifths',\n",
" 'ks_mode',\n",
" 'ts_beats',\n",
" 'ts_beat_type',\n",
" 'ts_mus_beats',\n",
" 'is_downbeat',\n",
" 'rel_onset_div',\n",
" 'tot_measure_div',\n",
" 'divs_pq')"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"extended_score_note_array.dtype.names"
]
},
{
"cell_type": "code",
"execution_count": 35,
"id": "crude-courage",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[('n2', 'C', 0, 4, -1, 1, 0) ('n1', 'C', 0, 5, -1, 1, 0)\n",
" ('n4', 'C', 0, 4, -1, 1, 0) ('n3', 'C', 0, 5, -1, 1, 0)\n",
" ('n6', 'C', 0, 4, -1, 1, 0) ('n5', 'C', 0, 5, -1, 1, 0)\n",
" ('n8', 'C', 0, 4, -1, 1, 1) ('n7', 'C', 0, 5, -1, 1, 1)\n",
" ('n10', 'C', 0, 4, -1, 1, 0) ('n9', 'C', 0, 5, -1, 1, 0)]\n"
]
}
],
"source": [
"print(extended_score_note_array[['id', \n",
" 'step', \n",
" 'alter', \n",
" 'octave', \n",
" 'ks_fifths', \n",
" 'ks_mode',\n",
" 'is_downbeat']][:10])"
]
},
{
"cell_type": "markdown",
"id": "greek-failure",
"metadata": {},
"source": [
"[//]:\n",
"
\n",
"\n",
"
"
]
},
{
"cell_type": "markdown",
"id": "170f1bea",
"metadata": {},
"source": [
"### 4.6 Extending note arrays with partitura.musicanalysis.note_features\n",
"\n",
"Note arrays can further be extended with note features. Note features are musical attributes that can be retrieved by built-in helper functions. See a list of avaliable note feature functions by calling `partitura.musicanalysis.list_note_feats_functions`. Pass a list of chosen note features to `partitura.compute_note_array` to compute an extended note array. "
]
},
{
"cell_type": "code",
"execution_count": 36,
"id": "2a7efe67",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['articulation_direction_feature',\n",
" 'articulation_feature',\n",
" 'duration_feature',\n",
" 'fermata_feature',\n",
" 'grace_feature',\n",
" 'loudness_direction_feature',\n",
" 'metrical_feature',\n",
" 'metrical_strength_feature',\n",
" 'onset_feature',\n",
" 'ornament_feature',\n",
" 'polynomial_pitch_feature',\n",
" 'relative_score_position_feature',\n",
" 'slur_feature',\n",
" 'staff_feature',\n",
" 'tempo_direction_feature',\n",
" 'time_signature_feature',\n",
" 'vertical_neighbor_feature']"
]
},
"execution_count": 36,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"pt.musicanalysis.list_note_feats_functions()"
]
},
{
"cell_type": "code",
"execution_count": 37,
"id": "53e8bfd2",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"array([('n2', -4., 1., -2. , 0.5, 0, 8, 60, 4, 1., 0., 0., 0., 0., 0., 0., 0.33333334, 0., 0., 0.),\n",
" ('n1', -4., 1., -2. , 0.5, 0, 8, 72, 1, 1., 0., 0., 0., 0., 0., 0., 0.33333334, 0., 0., 0.),\n",
" ('n4', -3., 2., -1.5, 1. , 8, 16, 60, 4, 0., 1., 0., 0., 0., 0., 0., 0.5 , 0., 1., 0.),\n",
" ('n3', -3., 2., -1.5, 1. , 8, 16, 72, 1, 0., 1., 0., 0., 0., 0., 0., 0.5 , 0., 1., 0.),\n",
" ('n6', -1., 1., -0.5, 0.5, 24, 8, 60, 4, 0., 0., 1., 0., 0., 0., 0., 0.8333333 , 0., 0., 0.),\n",
" ('n5', -1., 1., -0.5, 0.5, 24, 8, 72, 1, 0., 0., 1., 0., 0., 0., 0., 0.8333333 , 0., 0., 0.),\n",
" ('n8', 0., 2., 0. , 1. , 32, 16, 60, 4, 1., 0., 0., 0., 0., 0., 0., 0. , 1., 0., 1.),\n",
" ('n7', 0., 2., 0. , 1. , 32, 16, 72, 1, 1., 0., 0., 0., 0., 0., 0., 0. , 1., 0., 1.),\n",
" ('n10', 2., 1., 1. , 0.5, 48, 8, 60, 4, 0., 0., 0., 1., 0., 0., 0., 0.33333334, 0., 0., 0.),\n",
" ('n9', 2., 1., 1. , 0.5, 48, 8, 72, 1, 0., 0., 0., 1., 0., 0., 0., 0.33333334, 0., 0., 0.)],\n",
" dtype=[('id', '\n",
"\n",
""
]
},
{
"cell_type": "markdown",
"id": "adjusted-fundamental",
"metadata": {},
"source": [
"### 4.8 Piano rolls\n",
"\n",
"Piano rolls are 2D matrices that represent pitch and time information. The time represents time steps (at a given resolution), while the pitch axis represents which notes are active at a given time step. We can think of piano rolls as the symbolic equivalent of spectrograms. \n",
"\n",
"#### Extracting a piano roll"
]
},
{
"cell_type": "code",
"execution_count": 39,
"id": "essential-academy",
"metadata": {},
"outputs": [],
"source": [
"score_fn = os.path.join(MUSICXML_DIR, 'Chopin_op10_no3.musicxml')\n",
"score = pt.load_musicxml(score_fn)\n",
"pianoroll = pt.utils.compute_pianoroll(score)"
]
},
{
"cell_type": "markdown",
"id": "entire-nitrogen",
"metadata": {},
"source": [
"The `compute_pianoroll` method has a few arguments to customize the resulting piano roll"
]
},
{
"cell_type": "code",
"execution_count": 40,
"id": "massive-monaco",
"metadata": {},
"outputs": [],
"source": [
"piano_range = True\n",
"time_unit = 'beat'\n",
"time_div = 10\n",
"pianoroll = pt.utils.compute_pianoroll(\n",
" note_info=score, # a `Score`, `Performance`, `Part`, `PerformedPart` or a note array\n",
" time_unit=time_unit, # beat, quarter, div, sec, etc. (depending on note_info)\n",
" time_div=time_div, # Number of cells per time unit\n",
" piano_range=piano_range # Use range of the piano (88 keys)\n",
")"
]
},
{
"cell_type": "markdown",
"id": "quality-coast",
"metadata": {},
"source": [
"An important thing to remember is that in piano rolls generated by `compute_pianoroll`, rows (the vertical axis) represent the pitch dimension and the columns (horizontal) the time dimension. \n",
"This results in a more intuitive way of plotting the piano roll. \n",
"For other applications the transposed version of this piano roll might be more useful (i.e., rows representing time steps and columns representing pitch information).\n",
"\n",
"Since piano rolls can result in very large matrices where most of the elements are 0, the output of `compute_pianoroll` is a [scipy sparse matrix](https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.csc_matrix.html). To convert it to a regular numpy array, we can simply use `pianoroll.toarray()`"
]
},
{
"cell_type": "markdown",
"id": "intended-answer",
"metadata": {},
"source": [
"Let's plot the piano roll!"
]
},
{
"cell_type": "code",
"execution_count": 41,
"id": "mature-dylan",
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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CgAAAAAAQEKAAgAAAAAAkBCgAAAAAAAAJAQoAAAAAAAAif55FwAREVmWbfNnS6VSj2wnL/+4f53Z19TW9r0rbXfls921nS3tb0/V21v01PVXRJ25Lnry+qvWce3O677WFPGeUevnr5rfyb39WFSTZ6rNq+a9trvuGUV8pooo5j2yiPryM1VndaVPdeaz1TyueV0HPXWsentNRdVT+1vN7VSrrWo+y/a1frMlXTmutf7vF53RU9cfm9eb+psRKAAAAAAAAIlOByirV6/ujjoAAAAAAAAKo9MBSkNDQ3z84x+PBx98sDvqAQAAAAAAyF2nA5Tvf//78fLLL8eRRx4Z++67b1xxxRWxdOnS7qgNAAAAAAAgF50OUI4//vi444474i9/+UucffbZcdNNN8Uee+wR73vf++K2226L119/vTvqBAAAAAAA6DGlLMuyrjbyzW9+Mz796U/HunXrYtiwYXH22WfHxRdfHDvssEM1auyStra2KJfLeZfBVnSlG5ZKpR7ZTl46s39bsrV9r9Z2imJL+1tr+9pVPXX9FVFPXRd5XX997brviiLeM2r9/FXzO7m3H4tq8ky1edXsJ0W8Z3Snvra/26ovP1N1Vlf6VF79sTdut7tqLmJNRdVT+1vN7VSrrWo+y/a1frMlXTmutf7vF3npjc/FRVCk/tba2hr19fWbXd5/WxtuaWmJG2+8MWbNmhUvvvhinHjiiXHmmWfGn//857jyyivj4Ycfjl/84hfb2jwAAAAAAEBuOh2g3HbbbXHDDTfEvffeGwceeGD867/+a3zkIx+JIUOGVNY5/PDD44ADDqhmnQAAAAAAAD2m0wHKGWecESeffHI89NBDceihh25ynREjRsS//du/dbk4+o6eGrZVpOFhPa2v7Xtf29+u6MvHqtbvPX353HZWEY9VEWuqplrfv7zU+n2tKPra/ve1/d1WjlPHdeVY9bXnqiIeqyLWVFS98Xu5Wm0VsaZa4PorHse19nX6HSivvPJKId5t0lHegQIAAAAAAKS29g6Ufp1tcIcddogXXngh/v3f/z1OOeWUWL58eURE3H333fHMM89se6UAAAAAAAAF0ekAZc6cOTFmzJiYN29e3HbbbbFq1aqIiHjyySfjsssuq3qBAAAAAAAAPa3TAcrFF18cl19+edx3330xcODAyvwjjzwyHn744aoWBwAAAAAAkIdOByhPP/10fOADH9ho/m677RYvvfRSVYoCAAAAAADIU6cDlCFDhsSyZcs2mv/EE0/Em970pqoUBQAAAAAAkKdOBygnn3xyfOYzn4nm5uYolUqxYcOGeOihh+Kiiy6Kj33sY91RIwAAAAAAQI8qZVmWdeYD69ati6lTp8asWbNi/fr10b9//1i/fn18+MMfjlmzZsV2223XXbVuk7a2tiiXy3mXAQAAAAAAFEhra2vU19dvdnmnA5Q3LFmyJJ5++ulYtWpVjB8/PvbZZ5949dVXY9CgQdtcbHfoCwHKNp7CXJVKpaq1taX9r+Z2YFNcf66/NzgWHVfE62ZL5yiverfWb/S57lHE/rklPXVPr/a2YFN62/UXUb3rohauv2p9L9XCsdiSau5frR+rzujJY9FTz2Ce9fLXlXPg/MHG/vG6KNJ1sLUApdM/4fWpT30qIiJGjhwZ73nPe+JDH/pQ7LPPPrF69ep4z3ves+2VAgAAAAAAFESnA5Sf/exncdlll7Wbt3r16jjmmGPi9ddfr1phAAAAAAAAeenf2Q/84he/iHe84x2x8847x7Rp02LlypUxefLk6N+/f9x9993dUSMAAAAAAECP6nSA8uY3vznuueeeeNe73hX9+vWLH/7wh1FXVxc/+9nPYscdd+yOGgEAAAAAAHpUpwOUiIixY8fGXXfdFe9+97tj4sSJcddddxXu5fEAAAAAAADbqkMByvjx46NUKm00v66uLpYuXRpHHHFEZd7jjz9eveoAAAAAAABy0KEA5fjjj+/mMgAAAAAAAIqjlGVZlncR3amtrS3K5XLeZQAAAAAAAAXS2toa9fX1m13erwdrAQAAAAAA6BUEKAAAAAAAAAkBCgAAAAAAQEKAAgAAAAAAkOhSgJJlWdT4O+gBAAAAAIA+aJsClO9973sxZsyYGDRoUAwaNCjGjh0b/+///b9q1wYAAAAAAJCL/p39wNVXXx2XXHJJnHvuuXHEEUdERMSDDz4YZ599drz00ktxwQUXVL1IAAAAAACAnlTKOvkbXKNHj47Pf/7z8bGPfazd/BtvvDE+97nPxaJFi6paYFe1tbVFuVzOuwwAAAAAAKBAWltbo76+frPLOz0CZdmyZXH44YdvNP/www+PZcuWdba5Xqda73wplUrbvJ2tfZbNK+I7ezpzPrdW/5ba6spne6OuXEOuv+7h+qtOHZ1pO6++3l3Hqog1be2zW1PE89dTqnlP8P3X84p4T4+o3n291q+/znKsisX117eeFXpKtY5rNY9FTx3XvM5fLfQbqqOa9zXfu39XxO+avq5ozzAdHXjR6Xeg7L333vGjH/1oo/m33HJL7LPPPp1tDgAAAAAAoHA6PQLl85//fJx00knxwAMPVN6B8tBDD8Xs2bM3GawAAAAAAAD0Np0egTJlypSYN29eDBs2LO6444644447YtiwYfHII4/EBz7wge6oEQAAAAAAoEd1egRKRMSECRPi+9//frVrAQAAAAAAKIRtClA2bNgQzz//fCxfvjw2bNjQbtk73/nOqhQGAAAAAACQl04HKA8//HB8+MMfjhdffDGyLGu3rFQqxfr166tWHAAAAAAAQB46HaCcffbZccghh8TPfvazGD58eJRKpe6oCwAAAAAAIDedDlCee+65+PGPfxx77713d9QDAAAAAACQu04HKBMnToznn3++zwYo1Rpxk/78WVe2s7W2+LvePmKqK/VXs09V8zhuaVvdtb89uX/V2m531tyVc9CZz7r+ukcR61JTz7Td258VivpdUsRjVUS9/Z4eUYzrr6eug65uq7c9V1Xz+aVaNW2t7c581vXXt54Vekq19qGax6KozwrVUgv9pjeqVl+o5vdfUa+bjirqf+vo6/f1Iuqtx7XTAcp5550XF154YTQ3N8eYMWNiwIAB7ZaPHTu2asUBAAAAAADkoZR18n+z69ev38aNlEqRZVkhXyLf1tYW5XI57zI2YgRKPnpr0tnTamEEyrZus6jbrYURKEB+ivisUNTvkiIeqyJyj++4WhiB0tu2W+sjUIDO8+8tvKGII1B6O99/9Hatra1RX1+/2eWdHoGyaNGiLhUEAAAAAABQdJ0OUPbYY4/uqAMAAAAAAKAwOh2gvOHZZ5+NxYsXx7p169rN/5d/+ZcuFwUAAAAAAJCnTgco//M//xMf+MAH4umnn668+yTi779JV7R3oAAAAAAAAHTWxm+E34rzzz8/Ro8eHcuXL48ddtghnnnmmXjggQfikEMOifvvv78bSgQAAAAAAOhZnR6BMnfu3PjVr34Vw4YNi379+kW/fv3i7W9/e8ycOTM+9alPxRNPPNEddQIAAAAAAPSYTgco69evj5122ikiIoYNGxZLly6N/fbbL/bYY49YuHBh1QusVW/85FnR2oKInu1TefTfvK6Zrmy3O2sual094Y2fodyczuzfltrq7ceJ3q/W+6DnKoqsp/pUb3y+yWu73VVzEWvqSdV6Fqrm8xm8Qb/hDdXqC/rU3/X17z9qX6cDlIMOOiiefPLJGD16dEycODGuuuqqGDhwYHz729+OvfbaqztqBAAAAAAA6FGdDlD+/d//PVavXh0REV/4whfife97X7zjHe+IXXbZJW655ZaqFwgAAAAAANDTStnWxsd2wMsvvxw777xzIYddtbW1RblczrsMAArIT3gBAFSHn/ACAHqj1tbWqK+v3+zyTo9A2ZShQ4dWoxkAAAAAAIBC6FCAcsIJJ8SsWbOivr4+TjjhhC2ue9ttt1WlMAAAAAAAgLx0KEApl8uVYbL19fWGzAIAAAAAADWtKu9AKTLvQAFgc7wDBQCgOrwDBQDojbb2DpR+HW1ow4YNceWVV8YRRxwRhx56aFx88cXx6quvVqXIiIgrrrgiSqVSTJs2rTJvzZo1MXXq1Nhll11i8ODBMWXKlGhpaanaNgG6KsuyLU7VaquINdVC/l4qlbY4Vautziri+atmP6jlPgXAtutt3395fpcWUU88U3W2LecPoLYU8fud2tfhAOWLX/xifPazn43BgwfHm970pvj6178eU6dOrUoRjz76aPzXf/1XjB07tt38Cy64IO6888649dZbY86cObF06dKtvoMFAAAAAACgqzocoHzve9+La6+9Nu69996444474s4774wf/OAHsWHDhi4VsGrVqjj11FPjO9/5Tuy8886V+a2trfHd7343rr766jjyyCNjwoQJccMNN8Rvf/vbePjhh7u0TQAAAAAAgC3pcICyePHieM973lP5e9KkSVEqlWLp0qVdKmDq1Knx3ve+NyZNmtRu/vz58+O1115rN3///fePUaNGxdy5czfb3tq1a6Otra3dBAAAAAAA0Bn9O7ri66+/Httvv327eQMGDIjXXnttmzd+8803x+OPPx6PPvroRsuam5tj4MCBMWTIkHbzGxoaorm5ebNtzpw5Mz7/+c9vc00AAAAAAAAdDlCyLIvTTz896urqKvPWrFkTZ599duy4446VebfddluH2luyZEmcf/75cd99920UzHTFjBkzYvr06ZW/29raYuTIkVVrHwAAAAAAqH0dDlBOO+20jeZ95CMf2eYNz58/P5YvXx4HH3xw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ZVl2xRVXZEOGDMl+8pOfZE899VT2/ve/Pxs9enT26quvVto45phjsvHjx2fz5s3LHnzwwWyfffbJTjnllLx2CTaypX6+cuXK7KKLLsrmzp2bLVq0KPvlL3+ZHXzwwdk+++yTrVmzptKGfk5vcM4552Tlcjm7//77s2XLllWmV155pbLO1p5ZXn/99eyggw7Kjj766GzBggXZPffck+26667ZjBkz8tgl2MjW+vnzzz+ffeELX8gee+yxbNGiRdlPfvKTbK+99sre+c53VtrQz+kNLr744mzOnDnZokWLsqeeeiq7+OKLs1KplP3iF7/Issz9nNqxpb7unk4t+6d/+qfs/PPPr/ztvv53NR+gZFmWffOb38xGjRqVDRw4MHvb296WPfzww3mXBF1y0kknZcOHD88GDhyYvelNb8pOOumk7Pnnn68sf/XVV7N//dd/zXbeeedshx12yD7wgQ9ky5Yty7Fi6Jhf//rXWURsNJ122mlZlmXZhg0bsksuuSRraGjI6urqsqOOOipbuHBhuzb+9re/Zaeccko2ePDgrL6+PjvjjDOylStX5rA3sGlb6uevvPJKdvTRR2e77rprNmDAgGyPPfbIzjrrrI3+xw/9nN5gU/08IrIbbrihsk5Hnln+9Kc/Zccee2w2aNCgbNiwYdmFF16Yvfbaaz28N7BpW+vnixcvzt75zndmQ4cOzerq6rK99947+/SnP521tra2a0c/p+g+/vGPZ3vssUc2cODAbNddd82OOuqoSniSZe7n1I4t9XX3dGpZGqC4r/9dKcuyrOfGuwAAAAAAABRfTb8DBQAAAAAAYFsIUAAAAAAAABICFAAAAAAAgIQABQAAAAAAICFAAQAAAAAASAhQAAAAAAAAEgIUAAAAAACAhAAFAAAAAAAgIUABAAA67fTTT4/jjz8+t+1/9KMfjS996UuVv/fcc8/42te+lls9PW3dunWx5557xmOPPZZ3KQAAULMEKAAAQDulUmmL0+c+97n4+te/HrNmzcqlvieffDJ+/vOfx6c+9ake3/a2BjUvvvhiDBo0KFatWhXPPPNMTJkyJfbcc88olUqbbe+aa66JPffcM7bffvuYOHFiPPLII5VlAwcOjIsuuig+85nPbOOeAAAAWyNAAQAA2lm2bFll+trXvhb19fXt5l100UVRLpdjyJAhudT3zW9+Mz74wQ/G4MGDc9n+tvjJT34S73rXu2Lw4MHxyiuvxF577RVXXHFFNDY2bnL9W265JaZPnx6XXXZZPP744zFu3LiYPHlyLF++vLLOqaeeGg8++GA888wzPbUbAADQpwhQAACAdhobGytTuVyOUqnUbt7gwYM3+gmvf/7nf47zzjsvpk2bFjvvvHM0NDTEd77znVi9enWcccYZsdNOO8Xee+8dd999d7tt/e53v4tjjz02Bg8eHA0NDfHRj340Xnrppc3Wtn79+vjxj38cxx133EbLVq5cGaecckrsuOOO8aY3vSmuueaadstXrFgRn/jEJ2LXXXeN+vr6OPLII+PJJ5+sLH/hhRfi/e9/fzQ0NMTgwYPj0EMPjV/+8pft9vHFF1+MCy64oDIaJ+J/R5ccd9xxsfPOO8eOO+4Yb3nLW+LnP/95u23/5Cc/iX/5l3+JiIhDDz00vvzlL8fJJ58cdXV1m9zPq6++Os4666w444wz4sADD4zrr78+dthhh/jv//7vyjo777xzHHHEEXHzzTdv9ngBAADbToACAABUxY033hjDhg2LRx55JM4777w455xz4oMf/GAcfvjh8fjjj8fRRx8dH/3oR+OVV16JiP8NNI488sgYP358PPbYY3HPPfdES0tLfOhDH9rsNp566qlobW2NQw45ZKNlX/7yl2PcuHHxxBNPxMUXXxznn39+3HfffZXlH/zgB2P58uVx9913x/z58+Pggw+Oo446Kl5++eWIiFi1alW85z3vidmzZ8cTTzwRxxxzTBx33HGxePHiiIi47bbbYvfdd48vfOELldE4ERFTp06NtWvXxgMPPBBPP/10XHnlle1Gx6xYsSIefPDBSoCyNevWrYv58+fHpEmTKvP69esXkyZNirlz57Zb921ve1v85je/6VC7AABA5/TPuwAAAKA2jBs3Lv793/89IiJmzJgRV1xxRQwbNizOOuusiIi49NJL47rrrounnnoqDjvssPjWt74V48ePb/cy+P/+7/+OkSNHxh//+MfYd999N9rGiy++GNttt13stttuGy074ogj4uKLL46IiH333Tceeuih+OpXvxrvfve748EHH4xHHnkkli9fXhn18ZWvfCXuuOOO+PGPfxyf/OQnY9y4cTFu3LhKe//xH/8Rt99+e/z0pz+Nc889N4YOHRrbbbdd7LTTTu1+emvx4sUxZcqUGDNmTERE7LXXXu3q+vnPfx5jx46NESNGdOg4vvTSS7F+/fpoaGhoN7+hoSH+8Ic/tJs3YsSIePHFFzvULgAA0DlGoAAAAFUxduzYyj9vt912scsuu1RChYioBAJvvMfjySefjF//+tcxePDgyrT//vtHxP/+nNamvPrqq1FXV1f5+ax/1NTUtNHfv//97yvbWrVqVeyyyy7ttrdo0aLKtlatWhUXXXRRHHDAATFkyJAYPHhw/P73v6+MQNmcT33qU3H55ZfHEUccEZdddlk89dRT7Zb/4893VdugQYMqI3oAAIDqMgIFAACoigEDBrT7u1QqtZv3RuixYcOGiPjfwOK4446LK6+8cqO2hg8fvsltDBs2LF555ZVYt25dDBw4sMO1rVq1KoYPHx7333//RsuGDBkSEREXXXRR3HffffGVr3wl9t577xg0aFCceOKJsW7dui22/YlPfCImT54cP/vZz+IXv/hFzJw5M/7zP/8zzjvvvFi3bl3cc8898dnPfrbDtQ4bNiy22267aGlpaTe/paVlo5fOv/zyy7Hrrrt2uG0AAKDjjEABAABycfDBB8czzzwTe+65Z+y9997tph133HGTn3nrW98aERHPPvvsRssefvjhjf4+4IADKttqbm6O/v37b7StYcOGRUTEQw89FKeffnp84AMfiDFjxkRjY2P86U9/atfmwIEDY/369Rtte+TIkXH22WfHbbfdFhdeeGF85zvfiYiI+++/P3beeed2Pw22NQMHDowJEybE7NmzK/M2bNgQs2fP3miUze9+97sYP358h9sGAAA6ToACAADkYurUqfHyyy/HKaecEo8++mi88MILce+998YZZ5yxyZAiImLXXXeNgw8+OB588MGNlj300ENx1VVXxR//+Me45ppr4tZbb43zzz8/IiImTZoUTU1Ncfzxx8cvfvGL+NOf/hS//e1v49/+7d/isccei4iIffbZJ2677bZYsGBBPPnkk/HhD3+4MlrmDXvuuWc88MAD8Ze//CVeeumliIiYNm1a3HvvvbFo0aJ4/PHH49e//nUluPnpT3+60c93rVu3LhYsWBALFiyIdevWxV/+8pdYsGBBPP/885V1pk+fHt/5znfixhtvjN///vdxzjnnxOrVq+OMM85o19ZvfvObOProoztz2AEAgA4SoAAAALkYMWJEPPTQQ7F+/fo4+uijY8yYMTFt2rQYMmRI9Ou3+X9V+cQnPhE/+MEPNpp/4YUXxmOPPRbjx4+Pyy+/PK6++uqYPHlyRPzvz4f9/Oc/j3e+851xxhlnxL777hsnn3xyvPjii5V3s1x99dWx8847x+GHHx7HHXdcTJ48OQ4++OB22/jCF74Qf/rTn+LNb35z5aez1q9fH1OnTo0DDjggjjnmmNh3333j2muvjYhNByhLly6N8ePHx/jx42PZsmXxla98JcaPHx+f+MQnKuucdNJJ8ZWvfCUuvfTSeOtb3xoLFiyIe+65p92L5efOnRutra1x4oknduawAwAAHVTKsizLuwgAAICOevXVV2O//faLW265ZaOftCqSxx9/PI488sj461//utH7YarhpJNOinHjxnXq/SoAAEDHGYECAAD0KoMGDYrvfe97lZ/QKqrXX389vvnNb3ZLeLJu3boYM2ZMXHDBBVVvGwAA+F9GoAAAAAAAACSMQAEAAAAAAEgIUAAAAAAAABICFAAAAAAAgIQABQAAAAAAICFAAQAAAAAASAhQAAAAAAAAEgIUAAAAAACAhAAFAAAAAAAgIUABAAAAAABI/P8BsSd6f2uBzQAAAABJRU5ErkJggg==",
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, ax = plt.subplots(1, figsize=(20, 10))\n",
"ax.imshow(pianoroll.toarray(), origin=\"lower\", cmap='gray', interpolation='nearest', aspect='auto')\n",
"ax.set_xlabel(f'Time ({time_unit}s/{time_div})')\n",
"ax.set_ylabel('Piano key' if piano_range else 'MIDI pitch')\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"id": "funky-tract",
"metadata": {},
"source": [
"In some cases, we want to know the \"coordinates\" of each of the notes in the piano roll. The `compute_pianoroll` method includes an option to return "
]
},
{
"cell_type": "code",
"execution_count": 42,
"id": "palestinian-owner",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[[59 0 4 59]\n",
" [40 4 6 40]\n",
" [40 4 12 40]\n",
" [56 4 6 56]\n",
" [64 4 8 64]]\n"
]
}
],
"source": [
"pianoroll, note_indices = pt.utils.compute_pianoroll(score_part, return_idxs=True)\n",
"\n",
"# MIDI pitch, start, end\n",
"print(note_indices[:5])"
]
},
{
"cell_type": "markdown",
"id": "economic-denial",
"metadata": {},
"source": [
"#### Generating a note array from a piano roll\n",
"\n",
"Partitura also includes a method to generate a note array from a piano roll, which can be used to generate a MIDI file. \n",
"This method would be useful, e.g., for music generation tasks"
]
},
{
"cell_type": "code",
"execution_count": 43,
"id": "parental-links",
"metadata": {},
"outputs": [],
"source": [
"pianoroll = pt.utils.compute_pianoroll(score_part)\n",
"\n",
"new_note_array = pt.utils.pianoroll_to_notearray(pianoroll)\n",
"\n",
"# We can export the note array to a MIDI file\n",
"ppart = pt.performance.PerformedPart.from_note_array(new_note_array)\n",
"\n",
"# pt.save_performance_midi(ppart, \"newmidi.mid\")"
]
},
{
"cell_type": "markdown",
"id": "2372b392",
"metadata": {},
"source": [
"### 4.9 Music Analysis\n",
"\n",
"Automatic music analysis is one of the main areas where partitura pipelines are useful. Furthermore, for comparison and non-state-of-the-art applications partitura also provides baseline algorithms for several music analysis tasks:\n",
"\n",
"|Returns| Method|Notes|\n",
"|:---|:---|:---|\n",
"|Voices | `partitura.musicanalysis.estimate_voices`| Estimate each note's voice |\n",
"|Key | `partitura.musicanalysis.estimate_key`| |\n",
"|Pitch Spelling | `partitura.musicanalysis.estimate_spelling`| Infer correct pitch spelling from MIDI pitches|\n",
"|Tonal Tension| `partitura.musicanalysis.estimate_tonaltension`||\n",
"|Meter, Beats, Tempo| `partitura.musicanalysis.estimate_time`||"
]
},
{
"cell_type": "markdown",
"id": "2d82391a",
"metadata": {},
"source": [
"## The end of the tutorial, the start of your yet untold adventures in symbolic music processing...\n",
"\n",
"Thank you for trying out partitura! We hope it serves you well. \n",
"\n",
"If you miss a particular functionality or encounter a bug, we appreciate it if you raise an issue on github: [Github Issues](https://github.com/CPJKU/partitura/issues)\n",
"\n",
"[](https://colab.research.google.com/github/CPJKU/partitura_tutorial/blob/main/notebooks/01_introduction/Partitura_tutorial.ipynb)"
]
}
],
"metadata": {
"colab": {
"authorship_tag": "ABX9TyNCzhR7KnjsrjKGf/HDyInO",
"include_colab_link": true,
"name": "Partitura tutorial",
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.10"
},
"vscode": {
"interpreter": {
"hash": "172a5ce276eb1d0d801a9beadd7c9d3aac7210a29cee87eb8cc563f5ca08be0c"
}
}
},
"nbformat": 4,
"nbformat_minor": 5
}