diff --git a/source/FifthGeneratedTuner.qml b/source/FifthGeneratedTuner.qml
index e923b21..abb1209 100644
--- a/source/FifthGeneratedTuner.qml
+++ b/source/FifthGeneratedTuner.qml
@@ -22,6 +22,7 @@ import FileIO 3.0
 import MuseScore 3.0
 import "libs/AccidentalUtils.js" as AccidentalUtils
 import "libs/DateUtils.js" as DateUtils
+import "libs/IterationUtils.js" as IterationUtils
 import "libs/NoteUtils.js" as NoteUtils
 import "libs/StringUtils.js" as StringUtils
 import "libs/TuningUtils.js" as TuningUtils
@@ -32,7 +33,7 @@ MuseScore
 	description: "Retune the selection, or the whole score if nothing is selected, using the specified fifth size.";
 	categoryCode: "playback";
 	thumbnailName: "FifthGeneratedTunerThumbnail.png";
-	version: "1.3.1";
+	version: "1.3.2";
 	
 	pluginType: "dialog";
 	property var padding: 10;
@@ -846,99 +847,15 @@ MuseScore
 		{
 			logger.log("Tuning notes.");
 			
-			curScore.startCmd();
-		
-			// Calculate the portion of the score to tune.
-			var cursor = curScore.newCursor();
-			var startStaff;
-			var endStaff;
-			var startTick;
-			var endTick;
-			cursor.rewind(Cursor.SELECTION_START);
-			if (!cursor.segment)
-			{
-				logger.log("Tuning the entire score.");
-				startStaff = 0;
-				endStaff = curScore.nstaves - 1;
-				startTick = 0;
-				endTick = curScore.lastSegment.tick + 1;
-			}
-			else
-			{
-				logger.log("Tuning only the current selection.");
-				startStaff = cursor.staffIdx;
-				startTick = cursor.tick;
-				cursor.rewind(Cursor.SELECTION_END);
-				endStaff = cursor.staffIdx;
-				if (cursor.tick == 0)
+			IterationUtils.iterate(
+				curScore,
 				{
-					// If the selection includes the last measure of the score,
-					// .rewind() overflows and goes back to tick 0.
-					endTick = curScore.lastSegment.tick + 1;
-				}
-				else
-				{
-					endTick = cursor.tick;
-				}
-				logger.trace("Tuning only ticks: " + startTick + " - " + endTick);
-				logger.trace("Tuning only staffs: " + startStaff + " - " + endStaff);
-			}
-			
-			// Loop on the portion of the score to tune.
-			for (var staff = startStaff; staff <= endStaff; staff++)
-			{
-				for (var voice = 0; voice < 4; voice++)
-				{
-					logger.log("Tuning Staff: " + staff + "; Voice: " + voice);
-					
-					cursor.voice = voice;
-					cursor.staffIdx = staff;
-					cursor.rewindToTick(startTick);
-					
-					while (cursor.segment && (cursor.tick < endTick))
-					{
-						// Tune notes.
-						if (cursor.element && (cursor.element.type == Element.CHORD))
-						{
-							var graceChords = cursor.element.graceNotes;
-							for (var i = 0; i < graceChords.length; i++)
-							{
-								var notes = graceChords[i].notes;
-								for (var j = 0; j < notes.length; j++)
-								{
-									try
-									{
-										notes[j].tuning = calculateTuningOffset(notes[j]);
-									}
-									catch (error)
-									{
-										logger.error(error);
-									}
-								}
-							}
-							
-							var notes = cursor.element.notes;
-							for (var i = 0; i < notes.length; i++)
-							{
-								try
-								{
-									notes[i].tuning = calculateTuningOffset(notes[i]);
-								}
-								catch (error)
-								{
-									logger.error(error);
-								}
-							}
-						}
-						
-						cursor.next();
-					}
-				}
-			}
+					"onNote": onNote
+				},
+				logger
+			);
 			
 			logger.log("Notes tuned: " + tunedNotes + " / " + totalNotes);
-			
-			curScore.endCmd();
 		}
 		catch (error)
 		{
@@ -950,27 +867,21 @@ MuseScore
 		}
 	}
 	
-	/**
-	 * Returns the amount of cents necessary to tune the input note to 31EDO.
-	 */
-	function calculateTuningOffset(note)
+	function onNote(note)
 	{
 		totalNotes += 1;
 		
-		logger.trace("Tuning note: " + NoteUtils.getNoteLetter(note) + " " + AccidentalUtils.getAccidentalName(note) + " " + NoteUtils.getOctave(note));
-		
 		try
 		{
+			logger.trace("Tuning note: " + NoteUtils.getNoteLetter(note) + " " + AccidentalUtils.getAccidentalName(note) + " " + NoteUtils.getOctave(note));
 			var tuningOffset = -TuningUtils.circleOfFifthsDistance(note, referenceNote) * fifthDeviation;
-			tunedNotes += 1;
 			logger.trace("Tuning offset: " + tuningOffset);
-			return tuningOffset;
+			note.tuning = tuningOffset;
+			tunedNotes += 1;
 		}
 		catch (error)
 		{
 			logger.error(error);
-			// Leave the tuning of the input note unchanged.
-			return note.tuning;
 		}
 	}
 	
diff --git a/source/libs/IterationUtils.js b/source/libs/IterationUtils.js
new file mode 100644
index 0000000..392b922
--- /dev/null
+++ b/source/libs/IterationUtils.js
@@ -0,0 +1,163 @@
+/*
+	A collection of functions and constants for iterating over a score.
+	Copyright (C) 2024 Alessandro Culatti
+
+	This program is free software: you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation, either version 3 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program.  If not, see <https://www.gnu.org/licenses/>.
+*/
+
+const VERSION = "1.0.0";
+
+const ELEMENT_STAFF_TEXT = 48;
+
+function iterate(curScore, actions, logger)
+{
+	let onStaffStart = actions.onStaffStart || null;
+	let onNewMeasure = actions.onNewMeasure || null;
+	let onKeySignatureChange = actions.onKeySignatureChange || null;
+	let onAnnotation = actions.onAnnotation || null;
+	let staffTextOnCurrentStaffOnly = actions.staffTextOnCurrentStaffOnly || true;
+	let onNote = actions.onNote || null;
+	
+	curScore.startCmd();
+	let cursor = curScore.newCursor();
+	
+	// Calculate the portion of the score to iterate on.
+	let startStaff;
+	let endStaff;
+	let startTick;
+	let endTick;
+	cursor.rewind(Cursor.SELECTION_START);
+	if (!cursor.segment)
+	{
+		logger.log("Tuning the entire score.");
+		startStaff = 0;
+		endStaff = curScore.nstaves - 1;
+		startTick = 0;
+		endTick = curScore.lastSegment.tick;
+	}
+	else
+	{
+		logger.log("Tuning only the current selection.");
+		startStaff = cursor.staffIdx;
+		startTick = cursor.tick;
+		cursor.rewind(Cursor.SELECTION_END);
+		endStaff = cursor.staffIdx;
+		if (cursor.tick == 0)
+		{
+			// If the selection includes the last note of the score, .rewind()
+			// overflows and goes back to tick 0.
+			endTick = curScore.lastSegment.tick;
+		}
+		else
+		{
+			endTick = cursor.tick;
+		}
+		logger.trace("Tuning only ticks: " + startTick + " - " + endTick);
+		logger.trace("Tuning only staffs: " + startStaff + " - " + endStaff);
+	}
+	
+	// Iterate on the score.
+	for (let staff = startStaff; staff <= endStaff; staff++)
+	{
+		for (let voice = 0; voice < 4; voice++)
+		{
+			logger.log("Tuning Staff: " + staff + "; Voice: " + voice);
+			
+			cursor.voice = voice;
+			cursor.staffIdx = staff;
+			cursor.rewindToTick(startTick);
+			
+			let previousKeySignature = cursor.keySignature;
+			
+			if (onStaffStart)
+			{
+				onStaffStart();
+			}
+			
+			// Loop on the element of the current staff.
+			while (cursor.segment && (cursor.tick <= endTick))
+			{
+				if (cursor.segment.tick == cursor.measure.firstSegment.tick)
+				{
+					if (onNewMeasure)
+					{
+						onNewMeasure();
+					}
+				}
+				
+				if (cursor.keySignature != previousKeySignature)
+				{
+					if (onKeySignatureChange)
+					{
+						onKeySignatureChange(cursor.keySignature);
+					}
+					previousKeySignature = cursor.keySignature;
+				}
+				
+				for (let i = 0; i < cursor.segment.annotations.length; i++)
+				{
+					let annotation = cursor.segment.annotations[i];
+					let annotationText = annotation.text;
+					if (annotationText)
+					{
+						if (onAnnotation)
+						{
+							if ((annotation.type === ELEMENT_STAFF_TEXT) && staffTextOnCurrentStaffOnly)
+							{
+								let annotationPart = annotation.staff.part;
+								if ((4 * staff >= annotationPart.startTrack) && (4 * staff < annotationPart.endTrack))
+								{
+									onAnnotation(annotation);
+								}
+							}
+							else
+							{
+								onAnnotation(annotation);
+							}
+						}
+					}
+				}
+				
+				if (cursor.element && (cursor.element.type == Element.CHORD))
+				{
+					let graceChords = cursor.element.graceNotes;
+					for (let i = 0; i < graceChords.length; i++)
+					{
+						let notes = graceChords[i].notes;
+						for (let j = 0; j < notes.length; j++)
+						{
+							if (onNote)
+							{
+								onNote(notes[j]);
+							}
+						}
+					}
+					
+					let notes = cursor.element.notes;
+					for (let i = 0; i < notes.length; i++)
+					{
+						if (onNote)
+						{
+							onNote(notes[i]);
+						}
+					}
+				}
+				
+				cursor.next();
+			}
+		}
+	}
+	
+	curScore.endCmd();
+}