diff --git a/.github/workflows/ci_build.yml b/.github/workflows/ci_build.yml
index f6bbe77946c..ca515fa61ad 100644
--- a/.github/workflows/ci_build.yml
+++ b/.github/workflows/ci_build.yml
@@ -100,11 +100,11 @@ jobs:
 
           - os: macos-latest
             compiler: xcode
-            version: "13.1"
+            version: "14.3"
 
           - os: macos-latest
             compiler: xcode
-            version: "14.2"
+            version: "15.3"
 
           - os: macos-11
             compiler: g++
diff --git a/.github/workflows/python-ci-wheel.yml b/.github/workflows/python-ci-wheel.yml
index ee9ee651098..2c337e47b39 100644
--- a/.github/workflows/python-ci-wheel.yml
+++ b/.github/workflows/python-ci-wheel.yml
@@ -23,13 +23,16 @@ jobs:
       fail-fast: false
       # Build the wheels for Linux, Windows and macOS
       matrix:
-        os: [macos-latest, windows-latest, ubuntu-latest]
+        os: [macos-13, macos-14, windows-latest, ubuntu-latest]
         python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
-        architecture: [x86, x64]
+        architecture: [x86, x64, arm64]
         include:
-          - os: macos-latest
+          - os: macos-13
             architecture: x64
-            platform_id: macosx_*
+            platform_id: macosx_x86_64
+          - os: macos-14
+            architecture: arm64
+            platform_id: macosx_arm64
           - os: windows-latest
             architecture: x64
             platform_id: win_amd64
@@ -40,24 +43,35 @@ jobs:
             architecture: x64
             platform_id: manylinux_x86_64
         exclude:
-          - os: macos-latest
+          - os: macos-13
             architecture: x86
+          - os: macos-13
+            architecture: arm64
+          - os: macos-14
+            architecture: x86
+          - os: macos-14
+            architecture: x64
           - os: ubuntu-latest
             architecture: x86
+          - os: ubuntu-latest
+            architecture: arm64
+          - os: windows-latest
+            architecture: arm64
 
     steps:
       #===============================================#
       # Set up
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
         with:
           fetch-depth: 0
 
-      - uses: nuget/setup-nuget@v1
+      - uses: nuget/setup-nuget@a21f25cd3998bf370fde17e3f1b4c12c175172f9 # v2.0.0
+        if: always() && runner.os == 'Windows'
         with:
           nuget-version: "latest"
 
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
         with:
           python-version: ${{ matrix.python-version }}
           architecture: ${{ matrix.architecture }}
@@ -82,16 +96,14 @@ jobs:
       #===============================================#
       # wheels
       - name: Build wheels
-        uses: pypa/cibuildwheel@v2.16.5
+        uses: pypa/cibuildwheel@8d945475ac4b1aac4ae08b2fd27db9917158b6ce # v2.17.0
         with:
           output-dir: wheelhouse
         env:
-          CIBW_SKIP: cp37-macosx_arm64
           CIBW_BUILD: ${{ env.CIBW_BUILD_IDENTIFIER }}
           CIBW_ARCHS_MACOS: x86_64 arm64
           CIBW_ENVIRONMENT_MACOS:
             MACOSX_DEPLOYMENT_TARGET=10.15
-          CIBW_TEST_SKIP: cp*-macosx_arm64
           CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014
           CIBW_MANYLINUX_I686_IMAGE: manylinux2014
           CIBW_BEFORE_ALL_MACOS: brew update && brew install swig
@@ -119,7 +131,7 @@ jobs:
       - name: Install from wheel on macOS
         working-directory: wheelhouse
         if: always() && runner.os == 'macOS'
-        run: python -m pip install ./*x86_64.whl
+        run: python -m pip install ./*.whl
 
       # Wildcard use is different with PowerShell
       # cf. https://stackoverflow.com/a/43900040
@@ -134,9 +146,9 @@ jobs:
 
       #===============================================#
       # Upload artifacts
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
         with:
-          name: cibuildwheel-${{ runner.os }}-python-${{ matrix.python-version }}
+          name: cibuildwheel-${{ runner.os }}-python-${{ matrix.python-version }}-${{ matrix.architecture }}
           path: ./wheelhouse/*.whl
 
   #===============================================#
@@ -149,12 +161,12 @@ jobs:
     steps:
       #===============================================#
       # Set up
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
         with:
           fetch-depth: 0
 
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
         with:
           python-version: "3.9"
 
@@ -210,7 +222,7 @@ jobs:
 
       #===============================================#
       # Upload artifact
-      - uses: actions/upload-artifact@v3
+      - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3
         with:
           name: sdist-${{ runner.os }}-python-3.9
           path: dist/*.tar.gz
@@ -226,10 +238,10 @@ jobs:
     steps:
       #===============================================#
       # Set up
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
 
       - name: Set up Python 3.9
-        uses: actions/setup-python@v4
+        uses: actions/setup-python@82c7e631bb3cdc910f68e0081d67478d79c6982d # v5.1.0
         with:
           python-version: "3.9"
           architecture: "x64"
@@ -244,7 +256,7 @@ jobs:
       #===============================================#
       # Prepare artifacts
       - name: Download artifacts
-        uses: actions/download-artifact@v3
+        uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7
         with:
           path: bindings/python/artifacts/
 
diff --git a/.github/workflows/tests_build.yml b/.github/workflows/tests_build.yml
index 042d65eaf72..a76db42cad0 100644
--- a/.github/workflows/tests_build.yml
+++ b/.github/workflows/tests_build.yml
@@ -35,7 +35,7 @@ jobs:
     runs-on: ubuntu-22.04
 
     steps:
-      - uses: FranzDiebold/github-env-vars-action@v2.7.0
+      - uses: FranzDiebold/github-env-vars-action@v2.8.0
       - name: Get Short SHA
         run: |
           echo "SHORT_SHA=`echo ${{ github.event.pull_request.head.sha }} | cut -c1-7`" >> $GITHUB_ENV
@@ -109,14 +109,14 @@ jobs:
           ls -al
 
       - name: Upload results as artefacts
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: test-suite-diff
           path: ${{ github.workspace }}/${{ env.OUTPUT_DIR }}/
 
       - name: Check existence of the log.md file
         id: check_files
-        uses: andstor/file-existence-action@v2
+        uses: andstor/file-existence-action@v3
         with:
           files: "${{ github.workspace }}/${{ env.OUTPUT_DIR }}/log.md"
 
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fdc3eedc0ce..bde5a9c1af1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,16 @@
 # Changelog
 
 ## [unreleased]
+
+## [4.2.1] - 2024-05-07
+* Fix GitHub actions (Python release only)
+
+## [4.2.0] - 2024-05-05
 * Support for `fTrem@unitdur` (@eNote-GmbH)
 * Upgrade to C++20
+* Update of the Midifile library
+* Fix lyric position in MIDI output
+* Fix string formatting output with some locale configurations (@ammatwain)
 
 ## [4.1.0] - 2023-12-15
 * Support for staves ordered by `scoreDef`
diff --git a/Verovio.podspec b/Verovio.podspec
index 93b69a5ae10..a9ec8abbf82 100644
--- a/Verovio.podspec
+++ b/Verovio.podspec
@@ -1,6 +1,6 @@
 Pod::Spec.new do |s|
   s.name           = 'Verovio'
-  s.version        = '4.2.0-dev'
+  s.version        = '4.3.0-dev'
   s.license        = { :type => 'LGPL', :file => 'COPYING' }
   s.homepage       = 'https://www.verovio.org/index.xhtml'
   s.authors        = { 'Contributors List' => 'https://github.com/rism-digital/verovio/graphs/contributors' }
diff --git a/bindings/java/pom.xml b/bindings/java/pom.xml
index 33ef0597eb8..12e049ef9e8 100644
--- a/bindings/java/pom.xml
+++ b/bindings/java/pom.xml
@@ -4,7 +4,7 @@
 
   <groupId>org.rism.verovio</groupId>
   <artifactId>VerovioToolkit</artifactId>
-  <version>4.2.0-dev</version>
+  <version>4.3.0-dev</version>
   <packaging>jar</packaging>
 
   <name>VerovioToolkit</name>
diff --git a/bindings/python/.pypi-version b/bindings/python/.pypi-version
index 84066c019f4..76d263aca3f 100644
--- a/bindings/python/.pypi-version
+++ b/bindings/python/.pypi-version
@@ -1,3 +1,3 @@
 # dummy file used by setup.py for counting revisions when publishing to test.pypi
 # counting can be reset by making a change to this file
-4.2.0
+4.3.0
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
index 5d2b688547a..5c6927714f9 100644
--- a/cmake/CMakeLists.txt
+++ b/cmake/CMakeLists.txt
@@ -57,6 +57,10 @@ else()
         add_definitions(-DDEBUG)
     endif()
 
+    if(WIN32)
+        include_directories(../include/win32)
+    endif()
+
     # jsonxx raises -Wdollar-in-identifier-extension
     # gcc 8.3.1 does not like -Wdollar-in-identifier-extension option.
     add_definitions(-Wall -W -pedantic -Wno-unused-parameter -Wno-dollar-in-identifier-extension)
diff --git a/codemeta.json b/codemeta.json
index 390078a93ba..6461b01b695 100644
--- a/codemeta.json
+++ b/codemeta.json
@@ -4,8 +4,8 @@
   "identifier": "Verovio",
   "name": "Verovio",
   "description": "Verovio is a fast, portable and lightweight open-source library for engraving Music Encoding Initiative (MEI) music scores into SVG.",
-  "softwareVersion": "4.2.0-dev",
-  "datePublished": "2023-12-15",
+  "softwareVersion": "4.3.0-dev",
+  "datePublished": "2024-05-07",
   "license": "https://www.gnu.org/licenses/lgpl-3.0",
   "programmingLanguage": [{
     "@type": "ComputerLanguage",
diff --git a/emscripten/npm/package.json b/emscripten/npm/package.json
index 68404c71027..ee8f09137b4 100644
--- a/emscripten/npm/package.json
+++ b/emscripten/npm/package.json
@@ -1,6 +1,6 @@
 {
   "name": "verovio",
-  "version": "4.2.0-alpha",
+  "version": "4.3.0-alpha",
   "description": "This is the stable version of the verovio package",
   "main": "dist/verovio-toolkit-wasm.js",
   "exports": {
diff --git a/include/midi/Binasc.h b/include/midi/Binasc.h
index 6aa57f09478..686cc407c7c 100644
--- a/include/midi/Binasc.h
+++ b/include/midi/Binasc.h
@@ -13,11 +13,11 @@
 #ifndef _BINASC_H_INCLUDED
 #define _BINASC_H_INCLUDED
 
-#include <iostream>
+#include <cstdlib>
 #include <fstream>
+#include <iostream>
 #include <string>
-#include <cstdlib>
-#include <stdlib.h> /* needed for MinGW */
+
 
 namespace smf {
 
@@ -149,6 +149,8 @@ class Binasc {
 		int  getWord        (std::string& word, const std::string& input,
 		                     const std::string& terminators, int index);
 
+		static const char *GMinstrument[128];
+
 };
 
 } // end of namespace smf
diff --git a/include/midi/MidiEvent.h b/include/midi/MidiEvent.h
index 39f141ee9b1..f8141d17bba 100644
--- a/include/midi/MidiEvent.h
+++ b/include/midi/MidiEvent.h
@@ -15,8 +15,11 @@
 #define _MIDIEVENT_H_INCLUDED
 
 #include "MidiMessage.h"
+
+#include <ostream>
 #include <vector>
 
+
 namespace smf {
 
 class MidiEvent : public MidiMessage {
@@ -63,6 +66,10 @@ class MidiEvent : public MidiMessage {
 
 };
 
+
+std::ostream& operator<<(std::ostream& out, MidiEvent& event);
+
+
 } // end of namespace smf
 
 #endif /* _MIDIEVENT_H_INCLUDED */
diff --git a/include/midi/MidiEventList.h b/include/midi/MidiEventList.h
index e3bfb13cf5b..f9b1e8538e2 100644
--- a/include/midi/MidiEventList.h
+++ b/include/midi/MidiEventList.h
@@ -14,8 +14,10 @@
 #define _MIDIEVENTLIST_H_INCLUDED
 
 #include "MidiEvent.h"
+
 #include <vector>
 
+
 namespace smf {
 
 class MidiEventList {
diff --git a/include/midi/MidiFile.h b/include/midi/MidiFile.h
index 4a363da5658..4972218f627 100644
--- a/include/midi/MidiFile.h
+++ b/include/midi/MidiFile.h
@@ -16,19 +16,24 @@
 
 #include "MidiEventList.h"
 
-#include <vector>
-#include <string>
-#include <istream>
 #include <fstream>
+#include <istream>
+#include <string>
+#include <vector>
 
-#define TIME_STATE_DELTA       0
-#define TIME_STATE_ABSOLUTE    1
-
-#define TRACK_STATE_SPLIT      0
-#define TRACK_STATE_JOINED     1
 
 namespace smf {
 
+enum {
+    TRACK_STATE_SPLIT  = 0, // Tracks are separated into separate vector postions.
+    TRACK_STATE_JOINED = 1  // Tracks are merged into a single vector position,
+};                          // like a Type-0 MIDI file, but reversible.
+
+enum {
+    TIME_STATE_DELTA    = 0, // MidiMessage::ticks are in delta time format (like MIDI file).
+    TIME_STATE_ABSOLUTE = 1  // MidiMessage::ticks are in absolute time format (0=start time).
+};
+
 class _TickTime {
 	public:
 		int    tick;
@@ -217,7 +222,7 @@ class MidiFile {
 		MidiEvent*         addTempo               (int aTrack, int aTick,
 		                                           double aTempo);
 		MidiEvent*         addKeySignature        (int aTrack, int aTick,
-		                                           int key, bool mode = 0);
+		                                           int fifths, bool mode = 0);
 		MidiEvent*         addTimeSignature       (int aTrack, int aTick,
 		                                           int top, int bottom,
 		                                           int clocksPerClick = 24,
diff --git a/include/midi/MidiMessage.h b/include/midi/MidiMessage.h
index 49bd88a73e7..b298e15b9a0 100644
--- a/include/midi/MidiMessage.h
+++ b/include/midi/MidiMessage.h
@@ -14,10 +14,12 @@
 #ifndef _MIDIMESSAGE_H_INCLUDED
 #define _MIDIMESSAGE_H_INCLUDED
 
+#include <iostream>
 #include <string>
 #include <utility>
 #include <vector>
 
+
 namespace smf {
 
 typedef unsigned char  uchar;
@@ -122,6 +124,12 @@ class MidiMessage : public std::vector<uchar> {
 		void           makePatchChange      (int channel, int patchnum);
 		void           makeTimbre           (int channel, int patchnum);
 		void           makeController       (int channel, int num, int value);
+		void           makePitchBend        (int channel, int lsb, int msb);
+		void           makePitchBend        (int channel, int value);
+		void           makePitchBendDouble  (int channel, double value);
+		void           makePitchbend        (int channel, int lsb, int msb) { makePitchBend(channel, lsb, msb); }
+		void           makePitchbend        (int channel, int value) { makePitchBend(channel, value); }
+		void           makePitchbendDouble  (int channel, double value) { makePitchBendDouble(channel, value); }
 
 		// helper functions to create various continuous controller messages:
 		void           makeSustain          (int channel, int value);
@@ -199,6 +207,9 @@ class MidiMessage : public std::vector<uchar> {
 };
 
 
+std::ostream& operator<<(std::ostream& out, MidiMessage& event);
+
+
 } // end of namespace smf
 
 
diff --git a/include/vrv/vrvdef.h b/include/vrv/vrvdef.h
index 9fe1acdbe03..bda87157121 100644
--- a/include/vrv/vrvdef.h
+++ b/include/vrv/vrvdef.h
@@ -39,7 +39,7 @@ namespace vrv {
 //----------------------------------------------------------------------------
 
 #define VERSION_MAJOR 4
-#define VERSION_MINOR 2
+#define VERSION_MINOR 3
 #define VERSION_REVISION 0
 // Adds "-dev" in the version number - should be set to false for releases
 #define VERSION_DEV true
diff --git a/src/iomusxml.cpp b/src/iomusxml.cpp
index 667e4d99a68..a7bb45ee05c 100644
--- a/src/iomusxml.cpp
+++ b/src/iomusxml.cpp
@@ -1658,9 +1658,17 @@ bool MusicXmlInput::ReadMusicXmlMeasure(
     for (pugi::xml_node::iterator it = node.begin(); it != node.end(); ++it) {
         // first check if there is a multi measure rest
         if (it->select_node(".//multiple-rest")) {
-            const int multiRestLength = it->select_node(".//multiple-rest").node().text().as_int();
+            const pugi::xml_node multiRestNode = it->select_node(".//multiple-rest").node();
+            const int multiRestLength = multiRestNode.text().as_int();
+            const std::string symbols = multiRestNode.attribute("use-symbols").as_string();
             MultiRest *multiRest = new MultiRest;
-            if (it->select_node(".//multiple-rest[@use-symbols='yes']")) multiRest->SetBlock(BOOLEAN_false);
+            if (symbols == "no") {
+                // default by MusicXML specification
+                multiRest->SetBlock(BOOLEAN_true);
+            }
+            else if (symbols == "yes") {
+                multiRest->SetBlock(BOOLEAN_false);
+            }
             multiRest->SetNum(multiRestLength);
             Layer *layer = SelectLayer(1, measure);
             AddLayerElement(layer, multiRest);
diff --git a/src/midi/Binasc.cpp b/src/midi/Binasc.cpp
index f49aa563201..446dc03b9b0 100644
--- a/src/midi/Binasc.cpp
+++ b/src/midi/Binasc.cpp
@@ -11,12 +11,37 @@
 
 #include "Binasc.h"
 
+#include <cstdlib>
 #include <sstream>
-#include <stdlib.h>
 
 
 namespace smf {
 
+const char* Binasc::GMinstrument[128] = {
+   	"acoustic grand piano",   "bright acoustic piano",  "electric grand piano",  "honky-tonk piano", "rhodes piano",   "chorused piano",
+   	"harpsichord",  "clavinet",  "celeste",   "glockenspiel",   "music box",  "vibraphone",
+   	"marimba",   "xylophone",  "tubular bells",  "dulcimer",    "hammond organ",   "percussive organ",
+   	"rock organ",   "church organ", "reed organ",   "accordion",   "harmonica", "tango accordion",
+   	"nylon guitar",  "steel guitar",  "jazz guitar",   "clean guitar",  "muted guitar",   "overdriven guitar",
+   	"distortion guitar",   "guitar harmonics",   "acoustic bass",    "fingered electric bass",  "picked electric bass",  "fretless bass",
+   	"slap bass 1",  "slap bass 2",  "synth bass 1",  "synth bass 2",  "violin",    "viola",
+   	"cello",     "contrabass",  "tremolo strings",   "pizzcato strings",  "orchestral harp",      "timpani",
+   	"string ensemble 1",   "string ensemble 2",   "synth strings 1",   "synth strings 1",   "choir aahs",     "voice oohs",
+   	"synth voices",    "orchestra hit",   "trumpet",   "trombone",  "tuba",      "muted trumpet",
+   	"frenc horn", "brass section",  "syn brass 1",  "synth brass 2",  "soprano sax",  "alto sax",
+   	"tenor sax",  "baritone sax",   "oboe",      "english horn",  "bassoon",   "clarinet",
+   	"piccolo",   "flute",     "recorder",  "pan flute",  "bottle blow",    "shakuhachi",
+   	"whistle",   "ocarina",   "square wave",   "saw wave",   "calliope lead",  "chiffer lead",
+   	"charang lead",   "voice lead",   "fifths lead",   "brass lead",  "newage pad",  "warm pad",
+   	"polysyn pad",   "choir pad",   "bowed pad",  "metallic pad",  "halo pad",   "sweep pad",
+   	"rain",    "soundtrack",  "crystal",   "atmosphere",  "brightness",  "goblins",
+   	"echoes",   "sci-fi",  "sitar",     "banjo",     "shamisen",  "koto",
+   	"kalimba",   "bagpipes",  "fiddle",    "shanai",   "tinkle bell",  "agogo",
+   	"steel drums", "woodblock", "taiko drum",     "melodoc tom",      "synth drum",    "reverse cymbal",
+   	"guitar fret noise",   "breath noise",   "seashore",  "bird tweet",    "telephone ring", "helicopter",
+   	"applause",  "gunshot"
+};
+
 //////////////////////////////
 //
 // Binasc::Binasc -- Constructor: set the default option values.
@@ -717,7 +742,9 @@ int Binasc::readMidiEvent(std::ostream& out, std::istream& infile,
 			output << " '" << std::dec << (int)byte1;
 			if (m_commentsQ) {
 				output << "\t";
-				comment += "patch-change";
+				comment += "patch-change (";
+				comment += GMinstrument[byte1 & 0x7f];
+				comment += ")";
 			}
 			break;
 		case 0xD0:    // channel pressure: 1 bytes
diff --git a/src/midi/MidiEvent.cpp b/src/midi/MidiEvent.cpp
index 945e3c532eb..0cf590d8c97 100644
--- a/src/midi/MidiEvent.cpp
+++ b/src/midi/MidiEvent.cpp
@@ -13,7 +13,7 @@
 
 #include "MidiEvent.h"
 
-#include <stdlib.h>
+#include <cstdlib>
 
 
 namespace smf {
@@ -278,6 +278,20 @@ double MidiEvent::getDurationInSeconds(void) const {
 }
 
 
+
+//////////////////////////////
+//
+// operator<<(MidiMessage) -- Print tick value followed by MIDI bytes for event.
+//     The tick value will be either relative or absolute depending on the state
+//     of the MidiFile object containing it.
+//
+
+std::ostream& operator<<(std::ostream& out, MidiEvent& event) {
+	out << event.tick << '(' << static_cast<MidiMessage&>(event) << ')';
+	return out;
+}
+
+
 } // end namespace smf
 
 
diff --git a/src/midi/MidiEventList.cpp b/src/midi/MidiEventList.cpp
index 52749c5d837..f38fefbc2ca 100644
--- a/src/midi/MidiEventList.cpp
+++ b/src/midi/MidiEventList.cpp
@@ -10,15 +10,14 @@
 // Description:   A class which stores a MidiEvents for a MidiFile track.
 //
 
-
 #include "MidiEventList.h"
 
-#include <vector>
 #include <algorithm>
+#include <cstdlib>
 #include <iterator>
 #include <utility>
+#include <vector>
 
-#include <stdlib.h>
 
 namespace smf {
 
@@ -54,8 +53,8 @@ MidiEventList::MidiEventList(const MidiEventList& other) {
 //
 
 MidiEventList::MidiEventList(MidiEventList&& other) {
-   list = std::move(other.list);
-   other.list.clear();
+	list = std::move(other.list);
+	other.list.clear();
 }
 
 
@@ -124,12 +123,12 @@ const MidiEvent& MidiEventList::last(void) const {
 //
 
 MidiEvent& MidiEventList::getEvent(int index) {
-   return *list[index];
+	return *list[index];
 }
 
 
 const MidiEvent& MidiEventList::getEvent(int index) const {
-   return *list[index];
+	return *list[index];
 }
 
 
@@ -141,10 +140,10 @@ const MidiEvent& MidiEventList::getEvent(int index) const {
 //
 
 void MidiEventList::clear(void) {
-	for (int i=0; i<(int)list.size(); i++) {
-		if (list[i] != NULL) {
-			delete list[i];
-			list[i] = NULL;
+	for (auto& item : list) {
+		if (item != NULL) {
+			delete item;
+			item = NULL;
 		}
 	}
 	list.resize(0);
@@ -245,10 +244,10 @@ int MidiEventList::push_back(MidiEvent& event) {
 
 void MidiEventList::removeEmpties(void) {
 	int count = 0;
-	for (int i=0; i<(int)list.size(); i++) {
-		if (list[i]->empty()) {
-			delete list[i];
-			list[i] = NULL;
+	for (auto& item : list) {
+		if (item->empty()) {
+			delete item;
+			item = NULL;
 			count++;
 		}
 	}
@@ -257,9 +256,9 @@ void MidiEventList::removeEmpties(void) {
 	}
 	std::vector<MidiEvent*> newlist;
 	newlist.reserve(list.size() - count);
-	for (int i=0; i<(int)list.size(); i++) {
-		if (list[i]) {
-			newlist.push_back(list[i]);
+	for (auto& item : list) {
+		if (item) {
+			newlist.push_back(item);
 		}
 	}
 	list.swap(newlist);
@@ -292,8 +291,8 @@ int MidiEventList::linkNotePairs(void) {
 	// dimension 3: List of active note-ons or note-offs.
 	std::vector<std::vector<std::vector<MidiEvent*>>> noteons;
 	noteons.resize(16);
-	for (int i=0; i<(int)noteons.size(); i++) {
-		noteons[i].resize(128);
+	for (auto& noteon : noteons) {
+		noteon.resize(128);
 	}
 
 	// Controller linking: The following General MIDI controller numbers are
@@ -433,7 +432,7 @@ void MidiEventList::clearLinks(void) {
 
 //////////////////////////////
 //
-// MidiEventList::clearSequence -- Remove any seqence serial numbers from
+// MidiEventList::clearSequence -- Remove any sequence serial numbers from
 //   MidiEvents in the list.  This will cause the default ordering by
 //   sortTracks() to be used, in which case the ordering of MidiEvents
 //   occurring at the same tick may switch their ordering.
@@ -454,7 +453,7 @@ void MidiEventList::clearSequence(void) {
 //   to preseve the order of MIDI messages in a track when they occur
 //   at the same tick time.  Particularly for use with joinTracks()
 //   or sortTracks().  markSequence will be done automatically when
-//   a MIDI file is read, in case the ordering of events occuring at
+//   a MIDI file is read, in case the ordering of events occurring at
 //   the same time is important.  Use clearSequence() to use the
 //   default sorting behavior of sortTracks() when events occur at the
 //   same time.  Returns the next serial number that has not yet been
diff --git a/src/midi/MidiFile.cpp b/src/midi/MidiFile.cpp
index fa1534d79df..55956c8ce79 100644
--- a/src/midi/MidiFile.cpp
+++ b/src/midi/MidiFile.cpp
@@ -15,14 +15,14 @@
 #include "MidiFile.h"
 #include "Binasc.h"
 
-#include <string>
-#include <vector>
-#include <iostream>
-#include <iomanip>
+#include <algorithm>
 #include <fstream>
-#include <sstream>
+#include <iomanip>
+#include <iostream>
 #include <iterator>
-#include <algorithm>
+#include <sstream>
+#include <string>
+#include <vector>
 
 
 namespace smf {
@@ -71,21 +71,21 @@ const char* MidiFile::GMinstrument[128] = {
 
 //////////////////////////////
 //
-// MidiFile::MidiFile -- Constuctor.
+// MidiFile::MidiFile -- Constructor.
 //
 
 MidiFile::MidiFile(void) {
 	m_events.resize(1);
-	for (int i=0; i<(int)m_events.size(); i++) {
-		m_events[i] = new MidiEventList;
+	for (auto &event : m_events) {
+		event = new MidiEventList;
 	}
 }
 
 
 MidiFile::MidiFile(const std::string& filename) {
 	m_events.resize(1);
-	for (int i=0; i<(int)m_events.size(); i++) {
-		m_events[i] = new MidiEventList;
+	for (auto &event : m_events) {
+		event = new MidiEventList;
 	}
 	read(filename);
 }
@@ -93,8 +93,8 @@ MidiFile::MidiFile(const std::string& filename) {
 
 MidiFile::MidiFile(std::istream& input) {
 	m_events.resize(1);
-	for (int i=0; i<(int)m_events.size(); i++) {
-		m_events[i] = new MidiEventList;
+	for (auto &event : m_events) {
+		event = new MidiEventList;
 	}
 	read(input);
 }
@@ -507,7 +507,7 @@ bool MidiFile::readSmf(std::istream& input) {
 		m_events[i]->clear();
 
 		// Read MIDI events in the track, which are pairs of VLV values
-		// and then the bytes for the MIDI message.  Running status messags
+		// and then the bytes for the MIDI message.  Running status messages
 		// will be filled in with their implicit command byte.
 		// The timestamps are converted from delta ticks to absolute ticks,
 		// with the absticks variable accumulating the VLV tick values.
@@ -597,7 +597,7 @@ bool MidiFile::write(std::ostream& out) {
 	shortdata = static_cast<ushort>(getNumTracks());
 	writeBigEndianUShort(out, shortdata);
 
-	// 5. write out the number of ticks per quarternote. (avoiding SMTPE for now)
+	// 5. write out the number of ticks per quarternote. (avoiding SMPTE for now)
 	shortdata = static_cast<ushort>(getTicksPerQuarterNote());
 	writeBigEndianUShort(out, shortdata);
 
@@ -774,7 +774,7 @@ bool MidiFile::writeHex(std::ostream& out, int width) {
 	int wordcount = 1;
 	int linewidth = width >= 0 ? width : 25;
 	for (int i=0; i<len; i++) {
-		int value = (unsigned char)tempstream.str()[i];
+		int value = (uchar)tempstream.str()[i];
 		out << std::hex << std::setw(2) << std::setfill('0') << value;
 		if (linewidth) {
 			if (i < len - 1) {
@@ -836,7 +836,7 @@ bool MidiFile::writeBinasc(std::ostream& output) {
 
 //////////////////////////////
 //
-// MidiFile::writeBinascWithComents -- write a standard MIDI
+// MidiFile::writeBinascWithComments -- write a standard MIDI
 //    file from data into the binasc format (ASCII version
 //    of the MIDI file), including commentary about the MIDI messages.
 //
@@ -939,8 +939,8 @@ int MidiFile::size(void) const {
 //
 
 void MidiFile::removeEmpties(void) {
-	for (int i=0; i<(int)m_events.size(); i++) {
-		m_events[i]->removeEmpties();
+	for (auto &event : m_events) {
+		event->removeEmpties();
 	}
 }
 
@@ -954,7 +954,7 @@ void MidiFile::removeEmpties(void) {
 //   a track when they occur at the same tick time.  Particularly
 //   for use with joinTracks() or sortTracks().  markSequence will
 //   be done automatically when a MIDI file is read, in case the
-//   ordering of m_events occuring at the same time is important.
+//   ordering of m_events occurring at the same time is important.
 //   Use clearSequence() to use the default sorting behavior of
 //   sortTracks().
 //
@@ -982,7 +982,7 @@ void MidiFile::markSequence(int track, int sequence) {
 
 //////////////////////////////
 //
-// MidiFile::clearSequence -- Remove any seqence serial numbers from
+// MidiFile::clearSequence -- Remove any sequence serial numbers from
 //   MidiEvents in the MidiFile.  This will cause the default ordering by
 //   sortTracks() to be used, in which case the ordering of MidiEvents
 //   occurring at the same tick may switch their ordering.
@@ -1311,7 +1311,7 @@ void MidiFile::deltaTicks(void) {
 //    absolute time, which means that the time field
 //    in the MidiEvent struct represents the exact tick
 //    time to play the event rather than the time since
-//    the last event to wait untill playing the current
+//    the last event to wait until playing the current
 //    event.
 //
 
@@ -1422,7 +1422,7 @@ int MidiFile::getFileDurationInTicks(void) {
 //    in units of quarter notes.  If the MidiFile is in delta tick mode,
 //    then temporarily got into absolute tick mode to do the calculations.
 //    Note that this is expensive, so you should normally call this function
-//    while in aboslute tick (default) mode.
+//    while in absolute tick (default) mode.
 //
 
 double MidiFile::getFileDurationInQuarters(void) {
@@ -1434,7 +1434,7 @@ double MidiFile::getFileDurationInQuarters(void) {
 //////////////////////////////
 //
 // MidiFile::getFileDurationInSeconds -- returns the duration of the
-//    logest track in the file.  The tracks must be sorted before
+//    longest track in the file.  The tracks must be sorted before
 //    calling this function, since this function assumes that the
 //    last MidiEvent in the track has the highest timestamp.
 //    The file state can be in delta ticks since this function
@@ -1906,7 +1906,7 @@ MidiEvent* MidiFile::addTimeSignature(int aTrack, int aTick, int top, int bottom
 //
 // MidiFile::addCompoundTimeSignature -- Add a time signature meta message
 //      (meta #0x58), where the clocksPerClick parameter is set to three
-//      eighth notes for compount meters such as 6/8 which represents
+//      eighth notes for compound meters such as 6/8 which represents
 //      two beats per measure.
 //
 // Default values:
@@ -2347,7 +2347,7 @@ int MidiFile::getTicksPerQuarterNote(void) const {
 		// setting for 25 frames a second with 40 subframes
 		// which means one tick per millisecond.  When SMPTE is
 		// being used, there is no real concept of the quarter note,
-		// so presume 60 bpm as a simiplification here.
+		// so presume 60 bpm as a simplification here.
 		// return 1000;
 	}
 	return m_ticksPerQuarterNote;
@@ -2696,7 +2696,7 @@ double MidiFile::linearSecondInterpolationAtTick(int ticktime) {
 // MidiFile::buildTimeMap -- build an index of the absolute tick values
 //      found in a MIDI file, and their corresponding time values in
 //      seconds, taking into consideration tempo change messages.  If no
-//      tempo messages are given (or untill they are given, then the
+//      tempo messages are given (or until they are given, then the
 //      tempo is set to 120 beats per minute).  If SMPTE time code is
 //      used, then ticks are actually time values.  So don't build
 //      a time map for SMPTE ticks, and just calculate the time in
@@ -2956,15 +2956,17 @@ int MidiFile::extractMidiData(std::istream& input, std::vector<uchar>& array,
 ulong MidiFile::readVLValue(std::istream& input) {
 	uchar b[5] = {0};
 
-	for (int i=0; i<5; i++) {
-		b[i] = readByte(input);
-		if (!status()) { return m_rwstatus; }
-		if (b[i] < 0x80) {
-			break;
-		}
-	}
+    for (uchar &item : b) {
+            item = readByte(input);
+            if (!status()) {
+                    return m_rwstatus;
+            }
+            if (item < 0x80) {
+                    break;
+            }
+    }
 
-	return unpackVLV(b[0], b[1], b[2], b[3], b[4]);
+    return unpackVLV(b[0], b[1], b[2], b[3], b[4]);
 }
 
 
@@ -3005,7 +3007,7 @@ ulong MidiFile::unpackVLV(uchar a, uchar b, uchar c, uchar d, uchar e) {
 //
 // MidiFile::writeVLValue -- write a number to the midifile
 //    as a variable length value which segments a file into 7-bit
-//    values and adds a contination bit to each.  Maximum size of input
+//    values and adds a continuation bit to each.  Maximum size of input
 //    aValue is 0x0FFFffff.
 //
 
@@ -3392,7 +3394,7 @@ std::string MidiFile::base64Encode(const std::string& input) {
 	output.reserve(((input.size()/3) + (input.size() % 3 > 0)) * 4);
 	int vala = 0;
 	int valb = -6;
-	for (unsigned char c : input) {
+	for (uchar c : input) {
 		vala = (vala << 8) + c;
 		valb += 8;
 		while (valb >=0) {
@@ -3423,7 +3425,7 @@ std::string MidiFile::base64Decode(const std::string& input) {
 	std::string output;
 	int vala = 0;
 	int valb = -8;
-	for (unsigned char c : input) {
+	for (uchar c : input) {
 		if (c == '=') {
 			break;
 		} else if (MidiFile::decodeLookup[c] == -1) {
diff --git a/src/midi/MidiMessage.cpp b/src/midi/MidiMessage.cpp
index 7b9eb33c289..5b19a36eb8a 100644
--- a/src/midi/MidiMessage.cpp
+++ b/src/midi/MidiMessage.cpp
@@ -14,10 +14,10 @@
 #include "MidiMessage.h"
 
 #include <cmath>
+#include <cstdlib>
+#include <iomanip>
 #include <iostream>
 #include <iterator>
-#include <stdlib.h>
-
 
 
 namespace smf {
@@ -315,11 +315,13 @@ bool MidiMessage::isMetaMessage(void) const {
 //
 
 bool MidiMessage::isNoteOff(void) const {
-	if (size() != 3) {
+	const MidiMessage& message = *this;
+	const vector<uchar>& chars = message;
+	if (message.size() != 3) {
 		return false;
-	} else if (((*this)[0] & 0xf0) == 0x80) {
+	} else if ((chars[0] & 0xf0) == 0x80) {
 		return true;
-	} else if ((((*this)[0] & 0xf0) == 0x90) && ((*this)[2] == 0)) {
+	} else if (((chars[0] & 0xf0) == 0x90) && (chars[2] == 0x00)) {
 		return true;
 	} else {
 		return false;
@@ -676,7 +678,7 @@ bool MidiMessage::isInstrumentName(void) const {
 //////////////////////////////
 //
 // MidiMessage::isLyricText -- Returns true if message is a meta message
-//      describing some lyric text (for karakoke MIDI files)
+//      describing some lyric text (for karaoke MIDI files)
 //      (meta message type 0x05).
 //
 
@@ -857,7 +859,7 @@ int MidiMessage::getKeyNumber(void) const {
 
 //////////////////////////////
 //
-// MidiMessage::getVelocity -- Return the key veolocity.  If the message
+// MidiMessage::getVelocity -- Return the key velocity.  If the message
 //   is not a note-on or a note-off, then return -1.  If the value is
 //   out of the range 0-127, then chop off the high-bits.
 //
@@ -961,7 +963,7 @@ void MidiMessage::setP1(int value) {
 
 //////////////////////////////
 //
-// MidiMessage::setP2 -- Set the second paramter value.
+// MidiMessage::setP2 -- Set the second paramater value.
 //     If the MidiMessage is too short, add extra spaces
 //     to allow for P2.  The command byte and/or the P1 value
 //     will be undefined if extra space needs to be added and
@@ -980,7 +982,7 @@ void MidiMessage::setP2(int value) {
 
 //////////////////////////////
 //
-// MidiMessage::setP3 -- Set the third paramter value.
+// MidiMessage::setP3 -- Set the third paramater value.
 //     If the MidiMessage is too short, add extra spaces
 //     to allow for P3.  The command byte and/or the P1/P2 values
 //     will be undefined if extra space needs to be added and
@@ -1364,7 +1366,7 @@ void MidiMessage::setSpelling(int base7, int accidental) {
 //        pc + octave * 7
 //     where pc is the numbers 0 through 6 representing the pitch classes
 //     C through B, the octave is MIDI octave (not the scientific pitch
-//     octave which is one less than the MIDI ocatave, such as C4 = middle C).
+//     octave which is one less than the MIDI octave, such as C4 = middle C).
 //     The second number is the accidental for the base-7 pitch.
 //
 
@@ -1544,8 +1546,8 @@ void MidiMessage::setMetaContent(const std::string& content) {
 	// add the size of the meta message data (VLV)
 	int dsize = (int)content.size();
 	std::vector<uchar> vlv = intToVlv(dsize);
-	for (int i=0; i<(int)vlv.size(); i++) {
-		this->push_back(vlv[i]);
+	for (uchar item : vlv) {
+		this->push_back(item);
 	}
 	std::copy(content.begin(), content.end(), std::back_inserter(*this));
 }
@@ -1764,6 +1766,61 @@ void MidiMessage::makeController(int channel, int num, int value) {
 
 
 
+/////////////////////////////
+//
+// MidiMessage::makePitchBend -- Create a pitch-bend message.  lsb is
+//     least-significant 7 bits of the 14-bit range, and msb is the
+//     most-significant 7 bits of the 14-bit range.  The range depth
+//     is determined by a setting in the synthesizer.  Typically it is
+//     +/- two semitones by default.  See MidiFile::setPitchBendRange()
+//     to change the default (or change to the typical default).
+//
+
+void MidiMessage::makePitchBend(int channel, int lsb, int msb) {
+	resize(0);
+	push_back(0xe0 | (0x0e & channel));
+	push_back(0x7f & lsb);
+	push_back(0x7f & msb);
+}
+
+//
+// value is a 14-bit number, where 0 is the lowest pitch of the range, and
+//    2^15-1 is the highest pitch of the range.
+//
+
+void MidiMessage::makePitchBend(int channel, int value) {
+	resize(0);
+	int lsb = value & 0x7f;
+	int msb = (value >> 7) & 0x7f;
+	push_back(0xe0 | (0x7f & channel));
+	push_back(lsb);
+	push_back(msb);
+}
+
+//
+// Input value is a number between -1.0 and +1.0.
+//
+
+void MidiMessage::makePitchBendDouble(int channel, double value) {
+	// value is in the range from -1 for minimum and 2^18 - 1 for the maximum
+	resize(0);
+	double dvalue = (value + 1.0) * (pow(2.0, 15.0));
+	if (dvalue < 0.0) {
+		dvalue = 0.0;
+	}
+	if (dvalue > pow(2.0, 15.0) - 1.0) {
+		dvalue = pow(2.0, 15.0) - 1.0;
+	}
+	ulong uivalue = (ulong)dvalue;
+	uchar lsb = uivalue & 0x7f;
+	uchar msb = (uivalue >> 7) & 0x7f;
+	push_back(0xe0 | (0x7f & channel));
+	push_back(lsb);
+	push_back(msb);
+}
+
+
+
 /////////////////////////////
 //
 // MidiMessage::makeSustain -- Create a sustain pedal message.
@@ -1999,8 +2056,8 @@ void MidiMessage::makeSysExMessage(const std::vector<uchar>& data) {
 
 	int msize = endindex - startindex + 2;
 	std::vector<uchar> vlv = intToVlv(msize);
-	for (int i=0; i<(int)vlv.size(); i++) {
-		this->push_back(vlv[i]);
+	for (uchar item : vlv) {
+		this->push_back(item);
 	}
 	for (int i=startindex; i<=endindex; i++) {
 		this->push_back(data.at(i));
@@ -2095,18 +2152,18 @@ void MidiMessage::makeMts2_KeyTuningsBySemitone(std::vector<std::pair<int, doubl
 	data.push_back((uchar)0x02);  // sub-ID#2 (note change)
 	data.push_back((uchar)program);  // tuning program number (0 - 127)
 	std::vector<uchar> vlv = intToVlv((int)mapping.size());
-	for (int i=0; i<(int)vlv.size(); i++) {
-		data.push_back(vlv[i]);
+	for (uchar item : vlv) {
+		data.push_back(item);
 	}
-	for (int i=0; i<(int)mapping.size(); i++) {
-		int keynum = mapping[i].first;
+	for (auto &item : mapping) {
+		int keynum = item.first;
 		if (keynum < 0) {
 			keynum = 0;
 		} else if (keynum > 127) {
 			keynum = 127;
 		}
 		data.push_back((uchar)keynum);
-		double semitones = mapping[i].second;
+		double semitones = item.second;
 		int sint = (int)semitones;
 		if (sint < 0) {
 			sint = 0;
@@ -2120,8 +2177,8 @@ void MidiMessage::makeMts2_KeyTuningsBySemitone(std::vector<std::pair<int, doubl
 		uchar msb = (value >> 7) & 0x7f;
 		data.push_back(msb);
 		data.push_back(lsb);
-	}
-	this->makeSysExMessage(data);
+    }
+    this->makeSysExMessage(data);
 }
 
 
@@ -2204,8 +2261,8 @@ void MidiMessage::makeTemperamentBad(double maxDeviationCents, int referencePitc
 		maxDeviationCents = 100.0;
 	}
 	std::vector<double> temperament(12);
-	for (int i=0; i<(int)temperament.size(); i++) {
-		temperament[i] = ((rand() / (double)RAND_MAX) * 2.0 - 1.0) * maxDeviationCents;
+	for (double &item : temperament) {
+		item = ((rand() / (double)RAND_MAX) * 2.0 - 1.0) * maxDeviationCents;
 	}
 	this->makeMts9_TemperamentByCentsDeviationFromET(temperament, referencePitchClass, channelMask);
 }
@@ -2294,6 +2351,30 @@ void MidiMessage::makeTemperamentMeantoneCommaHalf(int referencePitchClass, int
 }
 
 
+
+//////////////////////////////
+//
+// operator<<(MidiMessage) -- Print MIDI messages as text.  0x80 and above
+//    are printed as hex, below as dec (will look strange for meta messages
+//    and system exclusives which could be dealt with later).
+//
+
+std::ostream& operator<<(std::ostream& out, MidiMessage& message) {
+	for (int i=0; i<(int)message.size(); i++) {
+		if (message[i] >= 0x80) {
+			out << "0x" << std::hex << std::setw(2) << std::setfill('0') << (int)message[i];
+			out << std::dec << std::setw(0) << std::setfill(' ');
+		} else {
+			out << (int)message[i];
+		}
+		if (i<(int)message.size() - 1) {
+			out << ' ';
+		}
+	}
+	return out;
+}
+
+
 } // end namespace smf