├── .args ├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README-en.md ├── README.md ├── abc2xml ├── abc2xml.html ├── abc2xml.py ├── abc2xml_changelog.html └── pyparsing.py ├── gabctk.py ├── midiutil ├── License.txt ├── MidiFile.py └── __init__.py ├── pylintrc ├── python.com └── windows ├── compilationsource ├── ReadMeGabc2mid.txt ├── help │ ├── gabc2mid.files │ │ └── img1.jpg │ ├── gabc2mid.hhp │ ├── gabc2mid.htm │ └── readme.txt ├── icon.ico └── versioninfo.py └── gabc2mid.zip /.args: -------------------------------------------------------------------------------- 1 | /zip/gabctk.py 2 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*" 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | - name: Create zip 15 | run: make release 16 | - name: Release 17 | uses: softprops/action-gh-release@v2 18 | with: 19 | files: '*.zip' 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gabctk.com 2 | 3 | *.py[cod] 4 | *~ 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Packages 10 | *.egg 11 | *.egg-info 12 | dist 13 | build 14 | eggs 15 | parts 16 | bin 17 | var 18 | sdist 19 | develop-eggs 20 | .installed.cfg 21 | lib 22 | lib64 23 | 24 | # Installer logs 25 | pip-log.txt 26 | 27 | # Unit test / coverage reports 28 | .coverage 29 | .tox 30 | nosetests.xml 31 | 32 | # Translations 33 | *.mo 34 | 35 | # Mr Developer 36 | .mr.developer.cfg 37 | .project 38 | .pydevproject 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2017 The Gregorio Project 2 | Contributions: Jacques Peron, BGMcoder 3 | 4 | This program is free software; you can redistribute it and/or modify it under the terms of the 5 | GNU General Public License as published by the Free Software Foundation; either version 2 of 6 | the License, or (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 9 | without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 10 | See the GNU General Public License for more details. . 11 | 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | com: 2 | cp python.com gabctk.com 3 | zip -r gabctk.com .args ./*.py midiutil/*.py abc2xml/*.py 4 | 5 | release: com 6 | zip gabctk.zip gabctk.com 7 | 8 | run: 9 | python gabctk.py 10 | -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 | Gabctk 2 | ====== 3 | 4 | Tool to work on gabc files. 5 | 6 | Gabc 7 | ---- 8 | 9 | *gabc* is the language used by the 10 | [Gregorio](https://gregorio-project.github.io/) 11 | software. 12 | You can find the description [here](https://gregorio-project.github.io/gabc/). 13 | 14 | 15 | Gabctk 16 | ------ 17 | 18 | This script is derived from [gabc2mid](https://github.com/jperon/gabc2mid); 19 | as the scope of the project has grown, gabc2mid will remain as it is now 20 | ( except for bug fixes), and the developments will take place here. 21 | Gabctk parses the *gabc* code, extracts what concerns the melody, 22 | and produces it as a midi and/or lilypond file, abc, 23 | musicxml file. 24 | It can also extract the text in a text file. 25 | The syntax is the following: 26 | 27 | gabctk.py -i \ 28 | [-n title] \ 29 | [-o ] \ 30 | [-l ] \ 31 | [-c ] \ 32 | [-x ] \ 33 | [-b ] \ 34 | [-e ] \ 35 | [-m ] \ 36 | [-t tempo] \ 37 | [-d transposition] \ 38 | [-a alert] \ 39 | [-v verbosity] 40 | 41 | All the options in square brackets are optional. `gabc -h` displays a short help. 42 | 43 | If, instead of a filename, you want to use the standard input or output, specify `-`. For example, to listen to a gabc with `timidity` : 44 | 45 | gabctk.py -i -o - | timidity - 46 | 47 | Or, to extract the text from the gabc and display it: 48 | 49 | gabctk.py -i -e - 50 | 51 | The tempo is expressed in beats per minute: 52 | its default value is 165. 53 | 54 | Transposition is expressed in semitones. In its absence, gabctk will automatically transpose the song to an easy-to-sing range. For the formats 55 | abc and musicxml formats, the management of the transposition is left to abc and the 56 | software compatible with these formats. The notes will therefore remain 57 | graphically in place, but the melody will be played at the pitch indicated by 58 | this parameter. 59 | 60 | If alerts are defined, gabctk will return a message each time it detects the 61 | it detects the string in the song text. 62 | For example, `gabctk.py -i \ -a j -a eumdem` will return a message 63 | if the text contains *j* or the word *eumdem*. 64 | 65 | It is also possible to convert several files at the same time. In this case, 66 | parameter to `-o`, `-l`, `-c`, `-x` or `-b` is a folder and not an individual file. For example, to convert to midi all 67 | gabc files in the current directory: 68 | 69 | gabctk.py -i *.gabc -o . 70 | 71 | Standalone executable 72 | --------------------- 73 | 74 | Thanks to [cosmopolitan](https://github.com/jart/cosmopolitan/), a standalone 75 | `gabctk.com` executable can be found in [Releases](https://github.com/jperon/gabctk/releases), 76 | or generated from sources with `make com` command (providden `zip` command is available to shell). 77 | Its use is identical to what’s described above, replacing `gabctk.py` by `gabctk.com`. 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Gabctk 2 | ====== 3 | 4 | Outil pour travailler sur les fichiers gabc. 5 | 6 | [English Documentation](README-en.md). 7 | 8 | Gabc 9 | ---- 10 | 11 | Le *gabc* est le langage utilisé par le logiciel 12 | [Gregorio](https://gregorio-project.github.io/). 13 | Vous en trouverez la description [ici](https://gregorio-project.github.io/gabc/). 14 | 15 | Gabctk 16 | ------ 17 | 18 | Ce script est dérivé de [gabc2mid](https://github.com/jperon/gabc2mid) ; 19 | l'optique du projet s'étant élargie, gabc2mid restera tel qu'il est à présent 20 | (sauf correction de bug), et les développements auront lieu ici. 21 | Gabctk parcourt le code *gabc*, en extrait ce qui concerne la mélodie, 22 | et produit celle-ci sous la forme d'un fichier midi et/ou lilypond, abc, 23 | musicxml. 24 | Il peut aussi extraire le texte dans un fichier texte. 25 | La syntaxe est la suivante : 26 | 27 | gabctk.py -i \ 28 | [-n titre] \ 29 | [-o ] \ 30 | [-l ] \ 31 | [-c ] \ 32 | [-x ] \ 33 | [-b ] \ 34 | [-e ] \ 35 | [-m ] \ 36 | [-t tempo] \ 37 | [-d transposition] \ 38 | [-a alerte] \ 39 | [-v verbosité] 40 | 41 | Toutes les options entre crochets sont facultatives. `gabc -h` affiche une aide sommaire. 42 | 43 | Si, à la place d'un nom de fichier, vous voulez utiliser l'entrée ou la sortie 44 | standard, spécifiez `-`. Par exemple, pour écouter un gabc grâce à `timidity` : 45 | 46 | gabctk.py -i -o - | timidity - 47 | 48 | Ou encore, pour extraire le texte du gabc et l'afficher : 49 | 50 | gabctk.py -i -e - 51 | 52 | Le tempo est exprimé en temps premiers par minute : 53 | sa valeur par défaut est 165. 54 | 55 | La transposition est exprimée en demi-tons. En son absence, gabctk transposera 56 | automatiquement le chant sur une tessiture facile à chanter. Pour les formats 57 | abc et musicxml, la gestion de la transposition est laissée à abc et aux 58 | différents logiciels compatibles avec ces formats. Les notes resteront donc 59 | graphiquement en place, mais la mélodie sera jouée à la hauteur indiquée par 60 | ce paramètre. 61 | 62 | Si des alertes sont définies, gabctk renverra un message chaque fois 63 | qu'il détecte la chaîne de caractères dans le texte du chant. 64 | Par exemple, `gabctk.py -i \ -a j -a eumdem` renverra un message 65 | si le texte contient des *j* ou le mot *eumdem*. 66 | 67 | Il est encore possible de convertir plusieurs fichiers à la fois. En ce cas, 68 | il faut donner en paramètre à `-o`, `-l`, `-c`, `-x` ou `-b` un dossier 69 | et non un fichier individuel. Par exemple, pour convertir en midi tous 70 | les gabc du répertoire courant : 71 | 72 | gabctk.py -i *.gabc -o . 73 | 74 | Exécutable autonome 75 | ------------------- 76 | 77 | Il est possible de récupérer dans [Releases](https://github.com/jperon/gabctk/releases) 78 | ou de créer soi-même un exécutable contenant tout ce qui est nécessaire pour utiliser 79 | gabctk, aussi bien sous Linux que sous MacOS ou encore Windows (grâce à 80 | [cosmopolitan](https://github.com/jart/cosmopolitan/)). Étant admis que le programme 81 | `zip` est accessible à l’interpréteur de commandes, `make com` devrait générer 82 | `gabctk.com`, utilisable comme décrit ci-dessus (en remplaçant `gabctk.py` 83 | par `gabctk.com`). 84 | -------------------------------------------------------------------------------- /abc2xml/abc2xml_changelog.html: -------------------------------------------------------------------------------- 1 | 2 |
2024-08-13
  3 | 
  4 | 	* Revision: 245
  5 | 	- again corrected an escape sequence, this time in pyparsing (always printed
  6 | 	syntax warning since python 3.12)
  7 | 
  8 | 2024-05-02
  9 | 
 10 | 	* Revision: 244
 11 | 	- corrected escape sequences that caused deprecation warnings (and syntax
 12 | 	warnings as of python 3.12)
 13 | 
 14 | 2024-04-11
 15 | 
 16 | 	* Revision: 243
 17 | 	- updated compile/distribute script to Py3
 18 | 
 19 | 2024-04-10
 20 | 
 21 | 	* Revision: 242
 22 | 	- implement !rbstop!, meaning: the current started volta will be stopped at
 23 | 	the next bar line (limitation of MusicXML)
 24 | 
 25 | 	* Revision: 241
 26 | 	- bug: incorrect parsing of tilde in lyrics (reported by Gáspár Erdélyi)
 27 | 
 28 | 2024-02-11
 29 | 
 30 | 	* Revision: 240
 31 | 	- initial-scale bug
 32 | 
 33 | 2023-03-29
 34 | 
 35 | 	* Revision: 239
 36 | 	- added a list of diagnostic messages (global info_list)
 37 | 	- only use sys.stdin.buffer when stdin is available (i.e. when run as main
 38 | 	program on the command line)
 39 | 	- only write diagnostic messages to stdout when run as main program
 40 | 	- added function getInfo() to read and clear the diagnostic messages when
 41 | 	abc2xml is imported as a library
 42 | 
 43 | 2022-09-27
 44 | 
 45 | 	* Revision: 238
 46 | 	- made pyparsing library (2.0.1) compatible with python 3.10 In version 3.10
 47 | 	the MutableMapping class was removed from the collections module.
 48 | 
 49 | 2022-05-14
 50 | 
 51 | 	* Revision: 237
 52 | 	- explicitly merge a backwards repeat when it appears at the end of an empty
 53 | 	overlay measure
 54 | 
 55 | 2022-01-21
 56 | 
 57 | 	* Revision: 236
 58 | 	- implement a separate slur stack for each overlay voice
 59 | 
 60 | 	* Revision: 235
 61 | 	- bug: slur numbers were wrong in voice overlay (reported by Chris Spencer)
 62 | 
 63 | 2021-11-24
 64 | 
 65 | 	* Revision: 234
 66 | 	- accept '-' as valid ABC input file name for reading from standard input
 67 | 
 68 | 2021-09-30
 69 | 
 70 | 	* Revision: 233
 71 | 	- retrieve score title from xml document when writing the output file when
 72 | 	the -t option is present (allows -t and -m to be used together)
 73 | 	- split a tune collection only on X: when it occurs at the beginning of a line
 74 | 
 75 | 2021-09-01
 76 | 
 77 | 	* Revision: 232
 78 | 	- correctly translate shorted tuplet notation (p:q
 79 | 
 80 | 2021-05-28
 81 | 
 82 | 	* Revision: 231
 83 | 	- added grammer for 13th chords
 84 | 	- also recognize min6, min7, etc in addition to m6 m7 etc.
 85 | 
 86 | 2021-05-27
 87 | 
 88 | 	* Revision: 230
 89 | 	- added translation of sus chords (sus, sus4 and sus2)
 90 | 
 91 | 2020-11-15
 92 | 
 93 | 	* Revision: 229
 94 | 	- added support for melisma's (contibuted by Michael Strasser) The
 95 | 	translation uses the lyric type attribute from MusicXML 3.0 for precise
 96 | 	layout, which is unfortunately not supported by MuseScore 3.5.2
 97 | 
 98 | 2020-07-23
 99 | 
100 | 	* Revision: 228
101 | 	- merged patch from Martin Tarenskeen with various corrections for Python3
102 | 	- bug: single slash division resulting in floats for <duration>
103 | 
104 | 2020-07-19
105 | 
106 | 	* Revision: 227
107 | 	- avoid rounding in computation of durations for 7 and 9 tupplets
108 | 	- use greatest common divisor to get the smallest <divisions> value, but not
109 | 	smaller than 120 for backwards compatibility.
110 | 
111 | 2020-05-19
112 | 
113 | 	* Revision: 226
114 | 	- add dummy instrument name in mkInst(). (bug MuseScore) MuseScore
115 | 	incorrectly places unpitched notes when no instrument name is present (empty
116 | 	instrument-name tag).
117 | 
118 | 
-------------------------------------------------------------------------------- /gabctk.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """GabcTk 5 | 6 | Ce programme vise à servir à toutes sortes de traitements sur les fichiers gabc 7 | (cf. [Gregorio](https://gregorio-project.github.io)). 8 | 9 | Actuellement, il permet de convertir les gabc en midi et en lilypond, et de 10 | vérifier si certains caractères n'ont pas été saisis dans le texte de la 11 | partition. 12 | 13 | """ 14 | 15 | # Librairies externes ################################################## 16 | 17 | import os 18 | import sys 19 | from argparse import ArgumentParser 20 | import re 21 | import unicodedata as ud 22 | sys.path.append(os.path.dirname(os.path.realpath(__file__))) 23 | from midiutil.MidiFile import MIDIFile # noqa 24 | from abc2xml import abc2xml # noqa 25 | abc2xml.info = lambda x, warn=1: x 26 | 27 | sys.setrecursionlimit(100000) 28 | 29 | # Variables globales ################################################### 30 | 31 | TITRE = "Cantus" 32 | H_LA = 57 # Le nombre correspond au "pitch" MIDI. 33 | TEMPO = 165 34 | DUREE_EPISEME = 1.7 35 | DUREE_AVANT_QUILISMA = 2 36 | DUREE_POINT = 2.3 37 | DEBUG = False 38 | ABC_ENTETE = ''' 39 | X: 1 40 | T: %(titre)s 41 | L: 1/8 42 | M: none 43 | K: clef=treble 44 | K: transpose=%(transposition)s 45 | U: P = !uppermordent! 46 | %(musique)s 47 | w: %(paroles)s 48 | ''' 49 | 50 | # pylint:disable=W1401 51 | LILYPOND_ENTETE = '''\\version "2.18" 52 | 53 | \\header { 54 | title = "%(titre)s" 55 | tagline = "" 56 | composer = "" 57 | } 58 | 59 | \\paper { 60 | #(include-special-characters) 61 | } 62 | 63 | MusiqueTheme = { 64 | \\key %(tonalite)s\\major 65 | %(musique)s} 66 | 67 | Paroles = \\lyricmode { 68 | %(paroles)s 69 | } 70 | 71 | \\score{ 72 | << 73 | \\new Staff << 74 | \\set Staff.midiInstrument = "flute" 75 | \\set Staff.autoBeaming = ##f 76 | \\new Voice = "theme" { 77 | \\cadenzaOn \\transpose c %(transposition)s{\\MusiqueTheme} 78 | } 79 | >> 80 | \\new Lyrics \\lyricsto theme { 81 | \\Paroles 82 | } 83 | >> 84 | \\layout{ 85 | \\context { 86 | \\Staff 87 | \\override TimeSignature #'stencil = #point-stencil 88 | \\override Slur #'stencil = ##f 89 | } 90 | } 91 | \\midi{} 92 | }''' 93 | 94 | 95 | # Méthodes globales #################################################### 96 | 97 | 98 | def aide(erreur, code, commande=os.path.basename(sys.argv[0])): 99 | """Affichage de l'aide""" 100 | # Tenir compte du message propre à chaque erreur, ainsi que du nom 101 | # sous lequel la commande a été appelée. 102 | sys.stderr.write( 103 | ('Erreur : ' + erreur + '\n' if erreur != '' else '') 104 | + 'Usage : \n ' 105 | + commande + ' ' 106 | + '-i \n ' 107 | + '[-o ]\n ' 108 | + '[-l ]\n ' 109 | + '[-c ]\n ' 110 | + '[-x ]\n ' 111 | + '[-e ]\n ' 112 | + '[-t ]\n ' 113 | + '[-d ]\n ' 114 | + '[-n ]\n ' 115 | + '[-a ]\n ' 116 | + '[-v]\n' 117 | ) 118 | # Renvoyer le code correspondant à l'erreur, 119 | # pour interagir avec d'autres programmes. 120 | sys.exit(code) 121 | 122 | 123 | def traiter_options(arguments): # pylint:disable=R0912 124 | """Fonction maîtresse""" 125 | # Analyse des arguments de la ligne de commande. 126 | args = ArgumentParser() 127 | args.add_argument('entree', nargs='*', help='Fichier gabc à traiter') 128 | args.add_argument( 129 | '-i', '--input', nargs='*', help='Fichier gabc à traiter' 130 | ) 131 | args.add_argument( 132 | '-o', '--midi', nargs='?', help='Sortie Midi', 133 | ) 134 | args.add_argument( 135 | '-l', '--lily', nargs='?', help='Sortie Lilypond' 136 | ) 137 | args.add_argument( 138 | '-c', '--abc', nargs='?', help='Sortie ABC' 139 | ) 140 | args.add_argument( 141 | '-x', '--mxml', nargs='?', help='Sortie MusicXML' 142 | ) 143 | args.add_argument( 144 | '-e', '--export', nargs='?', 145 | help='Ficher texte où exporter les paroles seules' 146 | ) 147 | args.add_argument( 148 | '-m', '--musique', nargs='?', 149 | help='Fichier texte où exporter les notes seules' 150 | ) 151 | args.add_argument( 152 | '-b', '--tab', nargs='?', help='Sortie tablature' 153 | ) 154 | args.add_argument( 155 | '-t', '--tempo', nargs=1, type=int, help='Tempo en notes par minute', 156 | default=TEMPO 157 | ) 158 | args.add_argument( 159 | '-d', '--transposition', nargs=1, type=int, 160 | help='Transposition en demi-tons' 161 | ) 162 | args.add_argument( 163 | '-n', '--titre', nargs=1, help='Titre de la pièce', 164 | ) 165 | args.add_argument( 166 | '-a', '--alerter', nargs='*', help='Caractères à signaler' 167 | ) 168 | args.add_argument( 169 | '-v', '--verbose', action='store_true', help='Degré de verbosité' 170 | ) 171 | opts = args.parse_args(arguments) 172 | if not opts.entree and opts.input: 173 | opts.entree = opts.input 174 | for entree in opts.entree: 175 | gabctk(entree, opts) 176 | 177 | 178 | def sansaccents(input_str): 179 | """Renvoie la chaîne d'entrée sans accents""" 180 | nkfd_form = ud.normalize('NFKD', input_str) 181 | return "".join([c for c in nkfd_form if not ud.combining(c)]).replace( 182 | '℣', 'V' 183 | )\ 184 | .replace('℟', 'R')\ 185 | .replace('æ', 'ae')\ 186 | .replace('œ', 'oe')\ 187 | .replace('ǽ', 'ae')\ 188 | .replace('œ́', 'oe') 189 | 190 | 191 | def sortie_verbeuse(debug, gabc, partition): 192 | """Affichage d'informations de débogage 193 | 194 | − les en-têtes gabc ; 195 | − la partition gabc (sans les en-têtes) ; 196 | − la partition (texte et ensemble syllabes/neumes). 197 | """ 198 | if debug: 199 | print(gabc.entetes, '\n') 200 | print(gabc.contenu, '\n') 201 | print(partition.texte) 202 | print(partition.syllabes) 203 | 204 | 205 | # pylint:disable=R0913 206 | def gabctk(entree, opts): 207 | """Export dans les différents formats""" 208 | transposition = opts.transposition[0] if opts.transposition else None 209 | tempo = opts.tempo if opts.tempo is int else TEMPO 210 | alertes = False 211 | # Extraire le contenu du gabc. 212 | try: 213 | f_gabc = FichierTexte(entree) 214 | gabc = Gabc(f_gabc.contenu) 215 | nom = f_gabc.nom 216 | # Si le gabc n'existe pas, afficher l'aide. 217 | except FileNotFoundError: 218 | aide('fichier inexistant', 2) 219 | # Extraire la partition. 220 | partition = gabc.partition(transposition=transposition) 221 | titre = \ 222 | opts.titre if opts.titre \ 223 | else gabc.entetes['name'] if 'name' in gabc.entetes \ 224 | else TITRE 225 | sortie_verbeuse(opts.verbose, gabc, partition) 226 | # Créer le fichier midi. 227 | if opts.midi: 228 | midi = Midi(partition, titre=titre, tempo=tempo) 229 | midi.ecrire(FichierTexte(opts.midi, nom, '.mid').chemin) 230 | # Créer le fichier lilypond 231 | if opts.lily: 232 | lily = Lily(partition, titre=titre, tempo=tempo) 233 | lily.ecrire(FichierTexte(opts.lily, nom, '.ly')) 234 | # Créer le fichier abc 235 | if opts.abc or opts.mxml: 236 | abc = Abc(partition, titre=titre, tempo=tempo) 237 | if opts.abc: 238 | abc.ecrire( 239 | FichierTexte(opts.abc, nom, '.abc'), abc=True 240 | ) 241 | if opts.mxml: 242 | abc.ecrire( 243 | FichierTexte(opts.mxml, nom, '.xml'), abc=False, xml=True 244 | ) 245 | # S'assurer de la présence de certains caractères, 246 | # à la demande de l'utilisateur. 247 | # Création d'une variable contenant les paroles. 248 | paroles = partition.texte 249 | # S'assurer des alertes définies par l'utilisateur. 250 | if opts.alerter: 251 | alertes = verifier(opts.alerter, paroles) 252 | # Si l'utilisateur l'a demandé, 253 | # écrire les paroles dans un fichier texte. 254 | if opts.export: 255 | FichierTexte(opts.export).ecrire(paroles + '\n') 256 | if opts.musique: 257 | FichierTexte(opts.musique).ecrire(partition.gabc) 258 | # Si l'utilisateur l'a demandé, 259 | # écrire une tablature dans un fichier texte. 260 | if opts.tab: 261 | tablature = re.sub( 262 | r'^\s+', '', 263 | '\n'.join( 264 | '{0}\t{1}'.format(syllabe, neume.ly) for syllabe, neume in 265 | zip(partition.syllabes, partition.musique) 266 | ).replace('\n ', '\n//\n') 267 | ) 268 | FichierTexte(opts.tab).ecrire(tablature + '\n') 269 | # Code d'erreur si des alertes ont été levées. 270 | sys.exit(16 if alertes else 0) 271 | 272 | 273 | def verifier(alertes, texte): 274 | """Contrôle de la présence de certains caractères 275 | 276 | (à la demande de l'utilisateur)""" 277 | n = False 278 | for alerte in alertes: 279 | if alerte in texte: 280 | n = True 281 | sys.stderr.write("!!! " + alerte + " !!!") 282 | return n 283 | 284 | 285 | # Classes ############################################################## 286 | 287 | # # Classes servant à l'analyse du gabc, de la mélodie et des paroles. 288 | 289 | 290 | class Gabc: 291 | """Description du fichier gabc""" 292 | def __init__(self, code): 293 | self.code = code 294 | 295 | @property 296 | def parties(self): 297 | """Tuple contenant d'une part les en-têtes, 298 | d'autre part le corps du gabc""" 299 | regex = re.compile('%%\r?\n') 300 | resultat = regex.split(self.code) 301 | return resultat 302 | 303 | @property 304 | def entetes(self): 305 | """En-têtes du gabc, sous forme d'un dictionnaire""" 306 | resultat = { 307 | info[0]: re.sub( 308 | '^ +| +$', '', 309 | ':'.join(info[1:]).replace(';', '').replace('\r', '') 310 | ) 311 | for info in [ 312 | ligne.split(':') 313 | for ligne in self.parties[0].split('\n') 314 | if ':' in ligne 315 | ] 316 | } 317 | categories = { 318 | 'alleluia': 'alleluia', 319 | 'antiphona': 'antiphona', 320 | 'antienne': 'antiphona', 321 | 'antiphon': 'antiphona', 322 | 'communio': 'communio', 323 | 'communion': 'communio', 324 | 'graduale': 'graduale', 325 | 'graduel': 'graduale', 326 | 'gradual': 'graduale', 327 | 'hymnus': 'hymnus', 328 | 'hymne': 'hymnus', 329 | 'hymn': 'hymnus', 330 | 'introitus': 'introitus', 331 | 'introit': 'introitus', 332 | 'kyriale': 'kyriale', 333 | 'lectio': 'lectio', 334 | 'leçon': 'lectio', 335 | 'lecon': 'lectio', 336 | 'lesson': 'lectio', 337 | 'offertorium': 'offertorium', 338 | 'offertoire': 'offertorium', 339 | 'offertory': 'offertorium', 340 | 'responsorium': 'responsorium', 341 | 'responsum': 'responsorium', 342 | 'répons': 'responsorium', 343 | 'repons': 'responsorium', 344 | 'response': 'responsorium', 345 | 'sequentia': 'sequentia', 346 | 'sequence': 'sequentia', 347 | 'tractus': 'tractus', 348 | 'trait': 'tractus', 349 | 'tract': 'tractus', 350 | 'versus': 'versus', 351 | 'verset': 'versus', 352 | 'verse': 'versus', 353 | } 354 | try: 355 | categorie = sansaccents(resultat['office-part'].lower()) 356 | if categorie in categories.keys(): 357 | resultat['office-part'] = categorie 358 | else: 359 | resultat['office-part'] = 'varia' 360 | except KeyError: 361 | resultat['office-part'] = 'varia' 362 | if 'name' not in resultat: 363 | resultat['name'] = TITRE 364 | return resultat 365 | 366 | @property 367 | def contenu(self): 368 | """Partition gabc sans en-têtes ni commentaires""" 369 | resultat = self.parties[1] 370 | resultat = re.sub('%.*\n', '', resultat) 371 | resultat = re.sub('\n', ' ', resultat) 372 | return resultat 373 | 374 | def partition(self, transposition=None): 375 | """Extraction de la partition à partir du contenu gabc""" 376 | contenu = self.contenu 377 | # Signes indiquant les commandes personnalisées (que l'on ignore). 378 | commandeperso = re.compile(r"\[[^\[^\]]*\]") 379 | contenu = commandeperso.sub('', contenu) 380 | # Signes indiquant que l'on passe du mode texte au mode musique. 381 | neume = re.compile(r"\([^\(\)]*\)") 382 | texte = re.compile(r"\)?[^\(\)]*\(") 383 | syllabes = [ 384 | txt.replace('(', '').replace(')', '') 385 | for txt in texte.findall(contenu) 386 | ] 387 | neumes = [ 388 | nme.replace('(', '').replace(')', '') 389 | for nme in neume.findall(contenu) 390 | ] 391 | partition = Partition( 392 | self.entetes['name'], transposition=transposition 393 | ) 394 | reprise = re.compile(r".*i*j\..*") 395 | for i, syllabe in enumerate(syllabes): 396 | rep = reprise.search(syllabe) 397 | if rep: 398 | syllabes[i - 2] += ' ' + rep.group(0) 399 | syllabes[i] = reprise.sub('', syllabe) 400 | # Extraction des différents signes 401 | mot = [] 402 | for txt, nme in zip(syllabes, neumes): 403 | try: 404 | if txt[0] == ' ': 405 | partition.append(Mot( 406 | gabc=mot, 407 | precedent=partition[-1] 408 | if len(partition) else None 409 | )) 410 | mot = [] 411 | except IndexError: 412 | partition.append(Mot( 413 | gabc=mot, 414 | precedent=partition[-1] 415 | if len(partition) else None 416 | )) 417 | mot = [] 418 | mot.append((txt, nme)) 419 | partition.append(Mot( 420 | gabc=mot, 421 | precedent=partition[-1] 422 | )) 423 | return partition 424 | 425 | 426 | class Partition(list): 427 | """Partition de musique. 428 | 429 | Une partition est un texte orné de musique. Elle contient donc des mots, 430 | eux-mêmes composés de syllabes, ces dernières ornées de neumes. 431 | 432 | """ 433 | def __init__(self, titre, transposition=None, *args, **params): 434 | list.__init__(self, *args, **params) 435 | self.titre = titre 436 | self.tonalite = ['c', 'M'] 437 | self._transposition = transposition 438 | 439 | @property 440 | def gabc(self): 441 | """Code gabc de la musique""" 442 | return re.sub( 443 | '(::|:|;)', '\\1\n', 444 | ' '.join(signe.gabc for signe in self.musique) 445 | ) 446 | 447 | @property 448 | def musique(self): 449 | """Liste de signes musicaux""" 450 | musique = [] 451 | for mot in self: 452 | musique += mot.musique 453 | return musique 454 | 455 | @property 456 | def syllabes(self): 457 | """Liste des syllabes des mots de la partition""" 458 | syllabes = [] 459 | for mot in self: 460 | syllabes += mot 461 | return syllabes 462 | 463 | @property 464 | def tessiture(self): 465 | """Notes extrêmes de la mélodie""" 466 | minimum = maximum = 0 467 | # Parcours de toutes les notes de la mélodie, pour déterminer 468 | # la plus haute et la plus basse. 469 | for neume in self.musique: 470 | for note in (notes for notes in neume if isinstance(notes, Note)): 471 | if minimum == 0 or note.hauteur < minimum: 472 | minimum = note.hauteur 473 | if note.hauteur > maximum: 474 | maximum = note.hauteur 475 | if self._transposition: 476 | minimum += self._transposition 477 | maximum += self._transposition 478 | return {'minimum': minimum, 'maximum': maximum} 479 | 480 | @property 481 | def texte(self): 482 | """Texte de la partition""" 483 | return ' '.join(str(mot) for mot in self) 484 | 485 | @property 486 | def transposition(self): 487 | """Transposition automatique de la partition si besoin""" 488 | if self._transposition is not None: 489 | return self._transposition 490 | else: 491 | # Calcul de la hauteur idéale. 492 | return 66 - int(sum(self.tessiture.values())/2) 493 | 494 | 495 | class ObjetLie: # pylint:disable=R0903 496 | """Objet lié au précédent 497 | 498 | Cette classe est celle dont héritent chacun des types d'éléments. Elle 499 | sert surtout à référencer l'élément précédent, de façon à simplifier 500 | certaines opérations rétroactives. 501 | 502 | """ 503 | def __init__(self, precedent): 504 | self._precedent = None 505 | self.precedent = precedent 506 | 507 | def __getattr__(self, attribut): 508 | try: 509 | return getattr(self.precedent, attribut) 510 | except AttributeError: 511 | raise 512 | 513 | def __setattr__(self, attribut, valeur): 514 | try: 515 | object.__setattr__(self, attribut, valeur) 516 | except AttributeError: 517 | try: 518 | setattr(self.precedent, attribut, valeur) 519 | except AttributeError: 520 | raise 521 | 522 | @property 523 | def precedent(self): 524 | """Renvoie la référence à l'objet précédent""" 525 | return self._precedent 526 | 527 | @precedent.setter 528 | def precedent(self, precedent): 529 | """Enregistre la référence de l'objet suivant""" 530 | self._precedent = precedent 531 | if precedent: 532 | self.precedent.suivant = self 533 | 534 | 535 | class Mot(ObjetLie, list): 536 | """Ensemble de syllabes 537 | 538 | Cet objet peut être défini à partir: 539 | 540 | - d'une liste d'objets Syllabe ; 541 | - d'une liste de tuples (syllabe, musique) en langage gabc. 542 | 543 | """ 544 | def __init__(self, gabc=None, precedent=None, *args, **params): 545 | ObjetLie.__init__(self, precedent=precedent) 546 | list.__init__(self, *args, **params) 547 | if gabc: 548 | for syl in gabc: 549 | self.append(Syllabe( 550 | gabc=syl, 551 | mot=self, 552 | precedent=( 553 | self[-1] if len(self) 554 | else self.precedent.dernieresyllabe if self.precedent 555 | else None 556 | ) 557 | )) 558 | self.dernieresyllabe = self[-1] 559 | 560 | def __repr__(self): 561 | return str(self) 562 | 563 | def __str__(self): 564 | return ''.join(str(syllabe) for syllabe in self) 565 | 566 | @property 567 | def musique(self): 568 | """Liste des signes musicaux du mot""" 569 | return [syllabe.musique for syllabe in self] 570 | 571 | 572 | class Syllabe(ObjetLie): 573 | """Ensemble de lettres, auquel est associé un neume 574 | 575 | Cet objet peut être défini à partir d'un tuple (syllabe, musique) 576 | en langage gabc. 577 | 578 | """ 579 | def __init__(self, gabc, mot=None, precedent=None): 580 | ObjetLie.__init__(self, precedent=precedent) 581 | self.mot = mot 582 | self.texte = gabc[0] 583 | if len(mot): 584 | try: 585 | alterations = precedent.musique[-1].alterations 586 | except AttributeError: 587 | alterations = None 588 | else: 589 | alterations = None 590 | self.neume = Neume( 591 | gabc=gabc[1], 592 | syllabe=self, 593 | alterations=alterations 594 | ) 595 | # Lilypond ne peut pas associer une syllabe à un "neume" sans note. 596 | # Il est donc nécessaire de traiter à part le texte pour lui. 597 | if ( 598 | self.precedent and self.precedent.ly_texte != '' 599 | and not self.precedent.neume.possede_note 600 | ): 601 | self.ly_texte = self.precedent.ly_texte + ' ' + self.texte 602 | self.precedent.ly_texte = '' 603 | else: 604 | self.ly_texte = self.texte 605 | try: 606 | while self.ly_texte[0] == ' ': 607 | self.ly_texte = self.ly_texte[1:] 608 | except IndexError: 609 | pass 610 | 611 | def __repr__(self): 612 | return str((self.texte, str(self.neume))) 613 | 614 | def __str__(self): 615 | return self.texte 616 | 617 | @property 618 | def ly(self): # pylint:disable=C0103 619 | """Texte de la syllabe adapté pour lilypond""" 620 | ly_texte = self.ly_texte 621 | special = re.compile(re.escape('') + '.*' + re.escape('')) 622 | if special.search(ly_texte): 623 | ly_texte = special.sub('', ly_texte) 624 | if ( 625 | not len(ly_texte) 626 | and not ( 627 | len(self.neume) == 1 628 | and isinstance(self.neume[0], Clef) 629 | ) 630 | ): 631 | ly_texte = '' 632 | ly_texte = ly_texte.replace(' ', '_') 633 | ly_texte = re.sub(r'([0-9]+\.?)', '\\\\set stanza = "\\1"', ly_texte) 634 | return ly_texte\ 635 | .replace('*', '‍*')\ 636 | .replace('', '').replace('', '')\ 637 | .replace('', '').replace('', '')\ 638 | .replace('{', '').replace('}', '')\ 639 | .replace('R/', '℟')\ 640 | .replace('V/', '℣')\ 641 | .replace('ae', 'æ')\ 642 | .replace("'ae", 'ǽ')\ 643 | .replace("", 'ǽ')\ 644 | .replace('AE', 'Æ')\ 645 | .replace("'AE", 'Ǽ')\ 646 | .replace("", 'Ǽ')\ 647 | .replace('oe', 'œ')\ 648 | .replace("'oe", 'œ́')\ 649 | .replace("", 'œ́')\ 650 | .replace('OE', 'Œ')\ 651 | .replace("'OE", 'Œ́')\ 652 | .replace("", 'Œ́') 653 | 654 | @property 655 | def abc(self): 656 | """Texte de la syllabe adapté pour abc""" 657 | abc_texte = self.texte 658 | try: 659 | if abc_texte[0] == ' ': 660 | abc_texte = abc_texte[1:] 661 | except IndexError: 662 | pass 663 | special = re.compile(re.escape('') + '.*' + re.escape('')) 664 | if special.search(abc_texte): 665 | abc_texte = special.sub('', abc_texte) 666 | if ( 667 | not len(abc_texte) 668 | and not ( 669 | len(self.neume) == 1 670 | and isinstance(self.neume[0], Clef) 671 | ) 672 | ): 673 | abc_texte = '' 674 | abc_texte = abc_texte.replace(' ', '~') 675 | return abc_texte\ 676 | .replace('-', '')\ 677 | .replace('*', '~✶').replace('~~', '~')\ 678 | .replace('', '').replace('', '')\ 679 | .replace('', '').replace('', '')\ 680 | .replace('{', '').replace('}', '')\ 681 | .replace('R/', '℟')\ 682 | .replace('V/', '℣')\ 683 | .replace('ae', 'æ')\ 684 | .replace("'ae", 'ǽ')\ 685 | .replace("", 'ǽ')\ 686 | .replace('AE', 'Æ')\ 687 | .replace("'AE", 'Ǽ')\ 688 | .replace("", 'Ǽ')\ 689 | .replace('oe', 'œ')\ 690 | .replace("'oe", 'œ́')\ 691 | .replace("", 'œ́')\ 692 | .replace('OE', 'Œ')\ 693 | .replace("'OE", 'Œ́')\ 694 | .replace("", 'Œ́') 695 | 696 | @property 697 | def musique(self): 698 | """Liste des notes associées à la syllabe""" 699 | return self.neume 700 | 701 | 702 | class Neume(list): 703 | """Ensemble de signes musicaux""" 704 | def __init__( 705 | self, gabc=None, syllabe=None, alterations=None, *args, **params 706 | ): 707 | list.__init__(self, *args, **params) 708 | self.syllabe = syllabe 709 | self.element_ferme = True 710 | self.possede_note = False 711 | self.alterations = alterations 712 | self.traiter_gabc(gabc) 713 | 714 | @property 715 | def gabc(self): 716 | """Code gabc du neume""" 717 | return( 718 | ''.join((signe.gabc for signe in self)) 719 | if len(self) 720 | else '' 721 | ) 722 | 723 | @property 724 | def ly(self): # pylint:disable=C0103 725 | """Expression lilypond du neume""" 726 | return ''.join(signe.ly for signe in self) 727 | 728 | @property 729 | def abc(self): 730 | """Expression lilypond du neume""" 731 | return ''.join(signe.abc for signe in self) 732 | 733 | def traiter_gabc(self, gabc): 734 | """Extraction des signes à partir du code gabc""" 735 | # Expression correspondant aux clés. 736 | cle = re.compile('[cf][b]?[1234]') 737 | # Ce dictionnaire renvoie l'objet correspondant à chaque signe. 738 | signes = { 739 | Note: re.compile("[abcdefghijklmABCDEFGHIJKLM]"), 740 | SigneRythmique: re.compile("[_.'w~]"), 741 | NoteSpeciale: re.compile("[osvOSV]"), 742 | Barre: re.compile("[`,;:]"), 743 | Alteration: re.compile("[xy#]"), 744 | Coupure: re.compile("[/ ]"), 745 | Custo: re.compile(r"\+"), 746 | Fin: re.compile("z"), 747 | Cesure: re.compile("!"), 748 | } 749 | if cle.search(gabc) and cle.fullmatch(gabc): 750 | # Traitement d'une clef toute simple (initiale) 751 | self.append(Clef( 752 | gabc=gabc, 753 | neume=self, 754 | )) 755 | elif cle.search(gabc): 756 | # Traitement des changements de clef 757 | clef = cle.findall(gabc)[0] 758 | autres = cle.split(gabc) 759 | for sgn in autres[0]: 760 | self.traiter_gabc(sgn) 761 | self.append(Clef( 762 | gabc=clef, 763 | neume=self, 764 | )) 765 | for sgn in autres[1]: 766 | self.traiter_gabc(sgn) 767 | else: 768 | for signe in gabc: 769 | for typesigne, regex in signes.items(): 770 | if regex.fullmatch(signe): 771 | try: 772 | alterations = self.alterations 773 | except AttributeError: 774 | alterations = None 775 | self.append(typesigne( 776 | gabc=signe, 777 | neume=self, 778 | precedent=self[-1] if len(self) 779 | else None, 780 | alterations=alterations 781 | )) 782 | for signe in self: 783 | if isinstance(signe, Note): 784 | signe.ouvrir_neume() 785 | break 786 | for signe in reversed(self): 787 | if isinstance(signe, Note): 788 | signe.fermer_neume() 789 | break 790 | 791 | 792 | class Signe(ObjetLie): 793 | """Signe musical 794 | 795 | Il peut s'agir d'une note, d'un épisème, d'une barre… 796 | 797 | """ 798 | def __init__( 799 | self, 800 | gabc, 801 | neume=None, 802 | precedent=None, 803 | suivant=None, 804 | alterations=None 805 | ): 806 | ObjetLie.__init__(self, precedent=precedent) 807 | self.gabc = gabc 808 | self.neume = neume 809 | self.suivant = suivant 810 | if alterations: 811 | self.alterations = alterations 812 | self._ly = '' 813 | self._abc = '' 814 | 815 | @property 816 | def ly(self): # pylint:disable=C0103 817 | """Code lilypond par défaut 818 | 819 | Cette méthode permet d'éviter qu'un signe n'ayant pas d'expression en 820 | ly renvoie l'expression de la note précédente. 821 | """ 822 | return self._ly 823 | 824 | @ly.setter 825 | def ly(self, valeur): # pylint:disable=C0103 826 | """'Setter' pour l'expression ly""" 827 | self._ly = valeur 828 | 829 | @property 830 | def abc(self): 831 | """Code abc par défaut 832 | 833 | Cette méthode permet d'éviter qu'un signe n'ayant pas d'expression en 834 | abc renvoie l'expression de la note précédente. 835 | """ 836 | return self._abc 837 | 838 | @abc.setter 839 | def abc(self, valeur): 840 | """'Setter' pour l'expression abc""" 841 | self._abc = valeur 842 | 843 | def __repr__(self): 844 | return str(type(self).__name__) + ' : ' + self.gabc 845 | 846 | def __str__(self): 847 | return self.gabc 848 | 849 | 850 | class Alteration(Signe): 851 | """Bémols et bécarres""" 852 | def __init__(self, gabc, **params): 853 | Signe.__init__(self, gabc, **params) 854 | self.gabc = self.precedent.gabc + gabc 855 | if self.precedent.premier_element: 856 | self.neume.element_ferme = True 857 | self.precedent = self.precedent.precedent 858 | self.neume.pop() 859 | 860 | @property 861 | def alterations(self): 862 | """Liste des altérations 863 | 864 | Sous forme d'un dictionnaire, où les notes marquées d'un bémol sont 865 | associées à la valeur -1, marquées d'un dièze à 1, les autres à 0. 866 | """ 867 | try: 868 | alterations = self.precedent.alterations 869 | except AttributeError: 870 | alterations = { 871 | chr(lettre): 0 for lettre in range(ord('a'), ord('p') + 1) 872 | } 873 | alterations[self.gabc[0]] = {'x': -1, 'y': 0, '#': 1}[self.gabc[1]] 874 | return alterations 875 | 876 | 877 | class Barre(Signe): 878 | """Barres délimitant les incises""" 879 | def __init__(self, gabc, **params): 880 | Signe.__init__(self, gabc, **params) 881 | if isinstance(self.precedent, Barre): 882 | if self.precedent.gabc == ':' and self.gabc == ':': 883 | self.gabc = self.precedent.gabc + gabc 884 | self.precedent = self.precedent.precedent 885 | self.neume.pop() 886 | else: 887 | raise ErreurSyntaxe('Double barre bizarre') 888 | try: 889 | self.precedent.duree_egaliser() 890 | except AttributeError: 891 | pass 892 | try: 893 | self.precedent.fermer_element() 894 | except AttributeError: 895 | pass 896 | self.poser_note_precedente() 897 | 898 | def poser(self, pose): 899 | """Ignorer le posé venant de la barre suivante""" 900 | pass 901 | 902 | def poser_note_precedente(self): 903 | """Augmente la durée de la note précédente""" 904 | pose = { 905 | "`": 0, 906 | ",": 0, 907 | ";": 0.5, 908 | ":": 1, 909 | "::": 1.2, 910 | }[self.gabc] 911 | try: 912 | self.precedent.poser(pose) 913 | except AttributeError: 914 | self.neume.syllabe.mot.precedent[-1].neume[-1].poser(pose) 915 | 916 | @property 917 | def ly(self): 918 | """Correspondance entre les barres gabc et les barres lilypond""" 919 | return ''' \\bar "{}"'''.format({ 920 | '': "", 921 | ',': "'", 922 | ';': "'", 923 | ':': "|", 924 | '::': "||" 925 | }[self.gabc]) 926 | 927 | @property 928 | def abc(self): 929 | """Correspondance entre les barres gabc et les barres abc""" 930 | return { 931 | '': "", 932 | ',': "!shortphrase![|]", 933 | ';': "!mediumphrase![|]", 934 | ':': "|", 935 | '::': "||" 936 | }[self.gabc] 937 | 938 | 939 | class Clef(Signe): 940 | """Clefs""" 941 | def __init__(self, gabc, **params): 942 | Signe.__init__(self, gabc, **params) 943 | self.neume.syllabe.mot.cle = self 944 | 945 | 946 | class Fin(Signe): 947 | """Le z de gabc 948 | 949 | Il signifie ou bien une fin de ligne forcée, ou bien un guidon automatique 950 | en fin de ligne. 951 | """ 952 | def __init__(self, gabc, **params): 953 | Signe.__init__(self, gabc, **params) 954 | 955 | 956 | class Custo(Signe): 957 | """Guidon en fin de ligne ou avant un changement de clef 958 | 959 | Il indique quelle est la note suivante. 960 | """ 961 | def __init__(self, gabc, **params): 962 | Signe.__init__(self, gabc, **params) 963 | self.precedent = self.precedent.precedent 964 | self.neume.pop() 965 | 966 | 967 | class Coupure(Signe): 968 | """Coupures neumatiques""" 969 | def __init__(self, gabc, **params): 970 | Signe.__init__(self, gabc, **params) 971 | try: 972 | self.precedent.duree_egaliser() 973 | except AttributeError: 974 | pass 975 | try: 976 | self.precedent.fermer_element() 977 | except AttributeError: 978 | pass 979 | 980 | def __repr__(self): 981 | if self.gabc == ' ': 982 | return '\\ ' 983 | else: 984 | return Signe.__repr__(self) 985 | 986 | 987 | class Cesure(Signe): 988 | """Césures neumatiques (symbole !)""" 989 | pass 990 | 991 | 992 | class SigneRythmique(Signe): 993 | """Épisèmes, points""" 994 | def __init__(self, gabc, **params): 995 | Signe.__init__(self, gabc, **params) 996 | try: 997 | self.precedent.appliquer({ 998 | "'": 'ictus', 999 | '_': 'episeme', 1000 | '.': 'point', 1001 | 'w': 'quilisma', 1002 | '~': 'liquescence', 1003 | }[self.gabc]) 1004 | except AttributeError: 1005 | print("Bizarrerie : signe rythmique sans note.") 1006 | 1007 | 1008 | class Note(Signe): 1009 | """Note de musique""" 1010 | def __init__(self, gabc, **params): 1011 | Signe.__init__( 1012 | self, 1013 | gabc=gabc, 1014 | **params 1015 | ) 1016 | self.hauteur = self.g2mid() 1017 | # Par défaut, la durée est à 1 : elle pourra être modifiée par 1018 | # la suite, s'il se rencontre un épisème, un point, etc. 1019 | self.duree = 1 1020 | self._ly = self.g2ly() 1021 | self._abc = self.g2abc() 1022 | self._nuances = [] 1023 | self.neume.possede_note = True 1024 | if self.neume.element_ferme: 1025 | self.ouvrir_element() 1026 | self.premier_element = True 1027 | else: 1028 | self.premier_element = False 1029 | 1030 | def duree_egaliser(self): 1031 | """Rend la durée de la note au moins égale à celle de la précédente""" 1032 | try: 1033 | if self.duree < self.precedent.duree: 1034 | self.duree = self.precedent.duree 1035 | except AttributeError: 1036 | pass 1037 | 1038 | def appliquer(self, nuance): 1039 | """Prise en compte des divers signes rythmiques""" 1040 | if nuance not in self._nuances: 1041 | self._nuances.append(nuance) 1042 | if nuance == 'episeme': 1043 | self.retenir(DUREE_EPISEME) 1044 | elif nuance == 'point': 1045 | self.retenir(DUREE_POINT) 1046 | self.fermer_element() 1047 | try: 1048 | self.precedent.fermer_element() 1049 | except AttributeError: 1050 | pass 1051 | elif nuance == 'quilisma': 1052 | self.precedent.retenir(DUREE_AVANT_QUILISMA) 1053 | else: 1054 | if nuance in ('quilisma', 'liquescence'): 1055 | raise ErreurSyntaxe('Deux {}s consécutifs.'.format(nuance)) 1056 | else: 1057 | self.precedent.appliquer(nuance) 1058 | 1059 | def poser(self, pose): 1060 | """Appliquer le posé réclamé par la barre suivante""" 1061 | self.duree += pose 1062 | 1063 | def retenir(self, duree): 1064 | """Définir la durée à cette valeur si elle n'est pas déjà supérieure""" 1065 | if self.duree < duree: 1066 | self.duree = duree 1067 | 1068 | @property 1069 | def ly(self): 1070 | # pylint:disable=C0103 1071 | ly = ' ' + self._ly 1072 | if 'point' in self._nuances: 1073 | ly = ly.replace('8', '4') 1074 | if 'episeme' in self._nuances: 1075 | ly += '--' 1076 | if 'ictus' in self._nuances: 1077 | ly += '-!' 1078 | if 'quilisma' in self._nuances: 1079 | ly += '\\prall' 1080 | if 'liquescence' in self._nuances: 1081 | ly = ' \\tiny{} \\normalsize'.format(ly) 1082 | return ly 1083 | 1084 | @property 1085 | def abc(self): 1086 | abc = self._abc 1087 | if 'point' in self._nuances: 1088 | while abc[-1] == ' ': 1089 | abc = abc[:-1] 1090 | abc = abc + '2' 1091 | if 'episeme' in self._nuances: 1092 | abc = '!tenuto!' + abc 1093 | if 'ictus' in self._nuances: 1094 | abc = '!wedge!' + abc 1095 | if 'quilisma' in self._nuances: 1096 | abc = 'P' + abc 1097 | if 'liquescence' in self._nuances: 1098 | pass 1099 | return abc 1100 | 1101 | def ouvrir_element(self): 1102 | """Indique à la note qu'elle ouvre un élément neumatique 1103 | 1104 | Ceci est surtout nécessaire pour lilypond 1105 | """ 1106 | self.neume.element_ferme = False 1107 | self._ly += '[' 1108 | 1109 | def fermer_element(self): 1110 | """Indique à la note qu'elle clôt un élément neumatique 1111 | 1112 | Ceci est surtout nécessaire pour lilypond 1113 | """ 1114 | self._abc += ' ' 1115 | if 'point' in self._nuances: 1116 | self._ly = self._ly.replace('[', '') 1117 | else: 1118 | self._ly = (self._ly + ']').replace('[]', '') 1119 | self.neume.element_ferme = True 1120 | 1121 | def ouvrir_neume(self): 1122 | """Indique à la note qu'elle ouvre un neume 1123 | 1124 | Ceci est surtout nécessaire pour lilypond 1125 | """ 1126 | self._ly += '(' 1127 | 1128 | def fermer_neume(self): 1129 | """Indique à la note qu'elle clôt un neume 1130 | 1131 | Ceci est surtout nécessaire pour lilypond 1132 | """ 1133 | self.duree_egaliser() 1134 | self._ly = (self._ly + ')').replace('()', '') 1135 | self.fermer_element() 1136 | 1137 | @property 1138 | def note(self): 1139 | """Renvoi du nom "canonique" de la note""" 1140 | octve = int(self.hauteur / 12) - 2 1141 | nte = int(self.hauteur % 12) 1142 | return ('Do', 1143 | 'Do#', 1144 | 'Ré', 1145 | 'Mib', 1146 | 'Mi', 1147 | 'Fa', 1148 | 'Fa#', 1149 | 'Sol', 1150 | 'Sol#', 1151 | 'La', 1152 | 'Sib', 1153 | 'Si')[nte] + str(octve) 1154 | 1155 | def g2ly(self): 1156 | """Renvoi du code lilypond correspondant à la note""" 1157 | octve = int(self.hauteur / 12) - 1 1158 | nte = int(self.hauteur % 12) 1159 | # Nom de la note 1160 | note = ('c', 1161 | 'cis', 1162 | 'd', 1163 | 'ees', 1164 | 'e', 1165 | 'f', 1166 | 'fis', 1167 | 'g', 1168 | 'gis', 1169 | 'a', 1170 | 'bes', 1171 | 'b')[nte] 1172 | # Hauteur de la note : 1173 | # on prévoit de la1 à sol7, ce qui est plutôt large ! 1174 | note += (", , ", 1175 | ", ", 1176 | "", 1177 | "'", 1178 | "''", 1179 | "'''", 1180 | "''''")[octve-1] 1181 | # Durée de la note : croche par défaut, pourra être précisée 1182 | # par la suite. 1183 | note += '8' 1184 | return note 1185 | 1186 | def g2abc(self): 1187 | """Renvoi du code abc correspondant à la note""" 1188 | # Nom de la note 1189 | return ( 1190 | 'A,', '_B' 'B,', 1191 | 'C', '_D', 'D', '_E', 'E', 'F', '_G', 'G', '_A', 'A', '_B', 'B', 1192 | 'c', '_d', 'd', '_e', 'e', 'f', '_g', 'g', '_a', 'a', '_b', 'b', 1193 | "c'", "_d'", "d'", "_e'", "e'", "f'", "_g'", "g'", "_a'", "a'" 1194 | )[self.hauteur - 58] 1195 | 1196 | def g2mid(self, gabc=None): 1197 | """Renvoi de la note correspondant à une lettre gabc""" 1198 | if not gabc: 1199 | gabc = self.gabc 1200 | # Définition de la gamme. 1201 | h_la = H_LA 1202 | gamme = { 1203 | 'notes': ('la', 'si', 'do', 're', 'mi', 'fa', 'sol'), 1204 | 'hauteurs': ( 1205 | h_la, 1206 | h_la + 2, 1207 | h_la + 3, 1208 | h_la + 5, 1209 | h_la + 7, 1210 | h_la + 8, 1211 | h_la + 10 1212 | ), 1213 | } 1214 | gabcnotes = "abcdefghijklm" 1215 | # Analyse de la clé : les lettres du gabc définissant une 1216 | # position sur la portée et non une hauteur de note, la note 1217 | # correspondant à une lettre dépend de la clé. 1218 | # N.B : c1, f1 et f2 n'existent pas normalement, mais cela ne 1219 | # nous regarde pas ! 1220 | cle = self.neume.syllabe.mot.cle.gabc 1221 | alterations = { 1222 | chr(lettre): 0 for lettre in range(ord('a'), ord('p') + 1) 1223 | } 1224 | try: 1225 | alterations = self.alterations if self.alterations else alterations 1226 | except AttributeError: 1227 | pass 1228 | # Traitement des bémols à la clé. 1229 | if len(cle) == 3: 1230 | cle = cle[0] + cle[2] 1231 | alterations[{ 1232 | "c4": 'bi', 1233 | "c3": 'g', 1234 | "c2": 'el', 1235 | "c1": 'cj', 1236 | "f4": 'fm', 1237 | "f3": 'dk', 1238 | "f2": 'bi', 1239 | "f1": 'g' 1240 | }[cle]] = -1 1241 | decalage = { 1242 | "c4": 0, 1243 | "c3": 2, 1244 | "c2": 4, 1245 | "c1": 6, 1246 | "f4": 3, 1247 | "f3": 5, 1248 | "f2": 0, 1249 | "f1": 2 1250 | } 1251 | i = decalage[cle] - 1 1252 | octve = -12 if cle == 'f3' else 0 1253 | hauteurs = {} 1254 | notes = {} 1255 | for j in gabcnotes: 1256 | i += 1 1257 | if i == 7: 1258 | i %= 7 1259 | octve += 12 1260 | notes[j] = gamme['notes'][i] 1261 | hauteurs[j] = gamme['hauteurs'][i] + octve 1262 | lettre = gabc.lower()[0] 1263 | hauteur = hauteurs[lettre] 1264 | hauteur += alterations[lettre] 1265 | # Si la note est altérée par un bémol, l'abaisser d'un demi-ton. 1266 | # N.B : le grégorien n'admet que le si bémol, mais il n'y avait 1267 | # pas de raison de se limiter à ce dernier. Cependant, on 1268 | # renvoie un avertissement si un autre bémol est rencontré, car 1269 | # il peut s'agir d'une erreur. 1270 | if alterations[lettre] == -1 and notes[lettre] != 'si': 1271 | sys.stderr.write(notes[lettre] + ' bémol rencontré') 1272 | return hauteur 1273 | 1274 | 1275 | class NoteSpeciale(Note): 1276 | """Notes répercutantes 1277 | 1278 | Virga, oriscus, stropha. 1279 | 1280 | """ 1281 | def __init__(self, gabc, precedent, **params): 1282 | Note.__init__(self, gabc=precedent.gabc, precedent=precedent, **params) 1283 | if isinstance(precedent, NoteSpeciale): 1284 | self.gabc = gabc 1285 | elif isinstance(precedent, Note): 1286 | self.gabc = self.precedent.gabc + gabc 1287 | if precedent.premier_element: 1288 | self.ouvrir_element() 1289 | self.precedent = precedent.precedent 1290 | self.neume.pop() 1291 | else: 1292 | raise ErreurSyntaxe(gabc + ' sans note précédente') 1293 | 1294 | def retenir(self, duree): 1295 | Note.retenir(self, duree) 1296 | 1297 | 1298 | # # Classes servant à l'export en différents formats. 1299 | 1300 | 1301 | class Lily: 1302 | """Partition lilypond""" 1303 | def __init__(self, partition, titre, tempo): 1304 | self.transposition = ( 1305 | "c, ", "des, ", "d, ", "ees, ", "e, ", "f, ", 1306 | "fis, ", "g, ", "aes, ", "a, ", "bes, ", "b, ", 1307 | "c", "des", "d", "ees", "e", "f", 1308 | "fis", "g", "aes", "a", "bes", "b", "c'" 1309 | )[(partition.transposition + 12) % 24] 1310 | self.tonalite = partition.tonalite[0] 1311 | self.texte, self.musique = self.traiter_partition(partition) 1312 | self.titre = titre 1313 | self.tempo = tempo 1314 | 1315 | @classmethod 1316 | def traiter_partition(cls, partition): 1317 | """Extraction du texte et des paroles depuis l'objet partition""" 1318 | texte = '' 1319 | musique = '' 1320 | i = 0 1321 | for mot in partition: 1322 | parole = ' -- '.join(syllabe.ly for syllabe in mot) 1323 | notes = ''.join(neume.ly for neume in mot.musique) 1324 | if len(parole) or len(notes): 1325 | i += 1 1326 | texte += ( 1327 | '%{}'.format(i) + 1328 | ('\n' + parole if len(parole) else '') 1329 | + '\n' 1330 | ) 1331 | musique += '%{}\n'.format(i) + notes + '\n' 1332 | return texte, musique 1333 | 1334 | def ecrire(self, fichier): 1335 | """Enregistrement du code lilypond dans un fichier""" 1336 | fichier.ecrire(LILYPOND_ENTETE % { 1337 | 'titre': self.titre, 1338 | 'tonalite': self.tonalite, 1339 | 'musique': self.musique, 1340 | 'transposition': self.transposition, 1341 | 'paroles': self.texte 1342 | }) 1343 | 1344 | 1345 | class Abc: 1346 | """Partition abc""" 1347 | def __init__(self, partition, titre, tempo): 1348 | self.tonalite = partition.tonalite[0] 1349 | self.texte, self.musique = self.traiter_partition(partition) 1350 | self.titre = titre 1351 | self.tempo = tempo / 2 1352 | self.texte, self.musique = self.traiter_partition(partition) 1353 | self.code = ABC_ENTETE % { 1354 | 'titre': self.titre, 1355 | 'tonalite': self.tonalite, 1356 | 'musique': self.musique, 1357 | 'transposition': partition.transposition, 1358 | 'paroles': self.texte 1359 | } 1360 | 1361 | @classmethod 1362 | def traiter_partition(self, partition): 1363 | """Création de la partition abc""" 1364 | texte = '' 1365 | musique = '' 1366 | for m, mot in enumerate(partition): 1367 | for i, syllabe in enumerate(mot): 1368 | syl = syllabe.abc 1369 | if i + 1 < len(mot): 1370 | syl = syl + '-' 1371 | notes = tuple(notes for notes in syllabe.musique) 1372 | for j, note in enumerate(notes): 1373 | musique += note.abc 1374 | if isinstance(note, Note) or isinstance(note, Alteration): 1375 | if j == 0: 1376 | texte += syl 1377 | if syl == '': 1378 | texte += '_' 1379 | elif not isinstance(notes[j - 1], Alteration): 1380 | texte += '_' 1381 | elif isinstance(note, Barre) and j == 0: 1382 | if texte[-2] == '_' and syl != '': 1383 | texte = texte[:-2] + syl 1384 | else: 1385 | texte = texte[:-1] + syl 1386 | texte += ' ' 1387 | musique += ' ' 1388 | return texte, musique[:-3] + '|]' 1389 | 1390 | def ecrire(self, fichier, abc=True, xml=False): 1391 | """Écriture effective du fichier abc""" 1392 | if abc: 1393 | fichier.ecrire(self.code) 1394 | if xml: 1395 | dossier, fichier = os.path.split(fichier.chemin) 1396 | fichier = '' if fichier == '-' else fichier 1397 | if fichier and not dossier: 1398 | dossier = '.' 1399 | abc2xml.convert( 1400 | dossier, fichier[:-4], self.code, False, False, False 1401 | ) 1402 | 1403 | 1404 | class MusicXML(Abc): 1405 | """Classe encapsulant la classe Abc pour produire du MusicXML""" 1406 | def __init__(self, partition, titre, tempo): 1407 | Abc.__init__(self, partition, titre, tempo) 1408 | 1409 | def ecrire(self, fichier): 1410 | Abc.ecrire(self, fichier, abc=False, xml=True) 1411 | 1412 | 1413 | class Midi: 1414 | """Musique midi""" 1415 | def __init__(self, partition, titre, tempo): 1416 | # Définition des paramètres MIDI. 1417 | piste = 0 1418 | temps = 0 1419 | self.tempo = tempo / 2 1420 | self.sortiemidi = MIDIFile(1, file_format=1) 1421 | # Nom de la piste. 1422 | self.sortiemidi.addTrackName(piste, temps, sansaccents(titre)) 1423 | # Tempo. 1424 | self.sortiemidi.addTempo(piste, temps, self.tempo) 1425 | # Instrument (74 : flûte). 1426 | self.sortiemidi.addProgramChange(piste, 0, temps, 74) 1427 | self.traiter_partition(partition, piste, temps) 1428 | 1429 | def traiter_partition(self, partition, piste, temps): 1430 | """Création des évènements MIDI""" 1431 | transposition = partition.transposition 1432 | channel = 0 1433 | volume = 127 1434 | for mot in partition: 1435 | for i, syllabe in enumerate(mot): 1436 | syl = str(syllabe) 1437 | if i + 1 < len(mot): 1438 | syl = syl + '-' 1439 | for j, note in enumerate( 1440 | notes for notes in syllabe.musique 1441 | if isinstance(notes, Note) 1442 | ): 1443 | pitch = note.hauteur + transposition 1444 | duree = int(note.duree) 1445 | self.sortiemidi.addTempo( 1446 | piste, temps, (self.tempo * duree / note.duree) 1447 | ) 1448 | self.sortiemidi.addNote( 1449 | piste, 1450 | channel, 1451 | pitch, 1452 | temps, 1453 | duree / 2, 1454 | volume 1455 | ) 1456 | if j == 0: 1457 | self.sortiemidi.addText( 1458 | piste, 1459 | temps, 1460 | syl 1461 | ) 1462 | temps += duree / 2 1463 | 1464 | def ecrire(self, chemin): 1465 | """Écriture effective du fichier MIDI""" 1466 | with ( 1467 | open(sys.stdout.fileno(), 'wb') 1468 | if chemin == '-' 1469 | else open(chemin, 'wb') 1470 | ) as sortie: 1471 | self.sortiemidi.writeFile(sortie) 1472 | 1473 | 1474 | # # Classe générique pour faciliter l'écriture de fichiers. 1475 | 1476 | 1477 | class FichierTexte(): 1478 | """Gestion des fichiers texte""" 1479 | def __init__(self, chemin, nom=None, ext=None): 1480 | if os.path.isdir(chemin): 1481 | chemin = os.path.join(chemin, nom + ext) 1482 | self.dossier = os.path.dirname(chemin) 1483 | self.nom = os.path.splitext(os.path.basename(chemin))[0] 1484 | self.chemin = chemin 1485 | 1486 | @property 1487 | def contenu(self): 1488 | """Lecture du contenu""" 1489 | if self.chemin == '-': 1490 | texte = sys.stdin.read(-1) 1491 | else: 1492 | with open(self.chemin, 'r', encoding='utf-8') as fichier: 1493 | texte = fichier.read(-1) 1494 | return texte 1495 | 1496 | def ecrire(self, contenu): 1497 | """Écriture dans le fichier""" 1498 | if self.chemin == '-': 1499 | sys.stdout.write(contenu) 1500 | else: 1501 | with open(self.chemin, 'w', encoding='utf-8') as fichier: 1502 | fichier.write(contenu) 1503 | 1504 | 1505 | class ErreurSyntaxe(Exception): 1506 | """Exception levée en cas d'erreur de syntaxe""" 1507 | pass 1508 | 1509 | 1510 | if __name__ == '__main__': 1511 | traiter_options(sys.argv[1:]) 1512 | -------------------------------------------------------------------------------- /midiutil/License.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------- 2 | MIDUTIL, Copyright (c) 2009-2016, Mark Conway Wirt 3 | 4 | 5 | This software is distributed under an Open Source license, the 6 | details of which follow. 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a 9 | copy of this software and associated documentation files (the "Software"), 10 | to deal in the Software without restriction, including without limitation 11 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 12 | and/or sell copies of the Software, and to permit persons to whom the 13 | Software is furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included 16 | in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR 22 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 23 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------- 26 | 27 | -------------------------------------------------------------------------------- /midiutil/MidiFile.py: -------------------------------------------------------------------------------- 1 | # ----------------------------------------------------------------------------- 2 | # Name: MidiFile.py 3 | # Purpose: MIDI file manipulation utilities 4 | # 5 | # Author: Mark Conway Wirt 6 | # 7 | # Created: 2008/04/17 8 | # Copyright: (c) 2009-2016 Mark Conway Wirt 9 | # License: Please see License.txt for the terms under which this 10 | # software is distributed. 11 | # ----------------------------------------------------------------------------- 12 | 13 | from __future__ import division, print_function 14 | import math 15 | import struct 16 | import warnings 17 | 18 | __version__ = 'HEAD' 19 | 20 | # TICKSPERQUARTERNOTE is the number of "ticks" (time measurement in the MIDI file) that 21 | # corresponds to one quarter note. This number is somewhat arbitrary, but should 22 | # be chosen to provide adequate temporal resolution. 23 | TICKSPERQUARTERNOTE = 960 24 | 25 | controllerEventTypes = {'pan': 0x0a} 26 | 27 | # Define some constants 28 | 29 | MAJOR = 0 30 | MINOR = 1 31 | SHARPS = 1 32 | FLATS = -1 33 | 34 | __all__ = ['MIDIFile', 'MAJOR', 'MINOR', 'SHARPS', 'FLATS'] 35 | 36 | 37 | class GenericEvent(object): 38 | ''' 39 | The event class from which specific events are derived 40 | ''' 41 | evtname = None 42 | sec_sort_order = 0 43 | 44 | def __init__(self, tick, insertion_order): 45 | self.tick = tick 46 | self.insertion_order = insertion_order 47 | 48 | def __eq__(self, other): 49 | ''' 50 | Equality operator. 51 | 52 | In the processing of the event list, we have need to remove duplicates. 53 | To do this we rely on the fact that the classes are hashable, and must 54 | therefore have an equality operator (__hash__() and __eq__() must both 55 | be defined). 56 | 57 | Some derived classes will need to override and consider their specific 58 | attributes in the comparison. 59 | ''' 60 | return (self.evtname == other.evtname and self.tick == other.tick) 61 | 62 | def __hash__(self): 63 | ''' 64 | Return a hash code for the object. 65 | 66 | This is needed in order to allow GenericObject classes to be used 67 | as the key in a dict or set. duplicate objects are removed from 68 | the event list by storing all the objects in a set, and then 69 | reconstructing the list from the set. The only real requirement 70 | for the algorithm is that the hash of equal objects must be equal. 71 | There is probably great opportunity for improvements in the hashing 72 | function. 73 | ''' 74 | # Robert Jenkin's 32 bit hash. 75 | a = int(self.tick) 76 | a = (a + 0x7ed55d16) + (a << 12) 77 | a = (a ^ 0xc761c23c) ^ (a >> 19) 78 | a = (a + 0x165667b1) + (a << 5) 79 | a = (a + 0xd3a2646c) ^ (a << 9) 80 | a = (a + 0xfd7046c5) + (a << 3) 81 | a = (a ^ 0xb55a4f09) ^ (a >> 16) 82 | return a 83 | 84 | 85 | class NoteOn(GenericEvent): 86 | ''' 87 | A class that encapsulates a note 88 | ''' 89 | evtname = 'NoteOn' 90 | midi_status = 0x90 # 0x9x is Note On 91 | sec_sort_order = 3 92 | 93 | def __init__(self, channel, pitch, tick, duration, volume, 94 | annotation=None, insertion_order=0): 95 | self.pitch = pitch 96 | self.duration = duration 97 | self.volume = volume 98 | self.channel = channel 99 | self.annotation = annotation 100 | super(NoteOn, self).__init__(tick, insertion_order) 101 | 102 | def __eq__(self, other): 103 | return (self.evtname == other.evtname and self.tick == other.tick and 104 | self.pitch == other.pitch and self.channel == other.channel) 105 | 106 | # In Python 3, a class which overrides __eq__ also needs to provide __hash__, 107 | # because in Python 3 parent __hash__ is not inherited. 108 | __hash__ = GenericEvent.__hash__ 109 | 110 | def __str__(self): 111 | return 'NoteOn %d at tick %d duration %d ch %d vel %d' % ( 112 | self.pitch, self.tick, self.duration, self.channel, self.volume) 113 | 114 | def serialize(self, previous_event_tick): 115 | """Return a bytestring representation of the event, in the format required for 116 | writing into a standard midi file. 117 | """ 118 | midibytes = b"" 119 | code = self.midi_status | self.channel 120 | varTime = writeVarLength(self.tick - previous_event_tick) 121 | for timeByte in varTime: 122 | midibytes += struct.pack('>B', timeByte) 123 | midibytes += struct.pack('>B', code) 124 | midibytes += struct.pack('>B', self.pitch) 125 | midibytes += struct.pack('>B', self.volume) 126 | return midibytes 127 | 128 | 129 | class NoteOff (GenericEvent): 130 | ''' 131 | A class that encapsulates a Note Off event 132 | ''' 133 | evtname = 'NoteOff' 134 | midi_status = 0x80 # 0x8x is Note Off 135 | sec_sort_order = 2 # must be less than that of NoteOn 136 | # If two events happen at the same time, the secondary sort key is 137 | # ``sec_sort_order``. Thus a class of events can be processed earlier than 138 | # another. One place this is used in the code is to make sure that note 139 | # off events are processed before note on events. 140 | 141 | def __init__(self, channel, pitch, tick, volume, 142 | annotation=None, insertion_order=0): 143 | self.pitch = pitch 144 | self.volume = volume 145 | self.channel = channel 146 | self.annotation = annotation 147 | super(NoteOff, self).__init__(tick, insertion_order) 148 | 149 | def __eq__(self, other): 150 | return (self.evtname == other.evtname and self.tick == other.tick and 151 | self.pitch == other.pitch and self.channel == other.channel) 152 | 153 | __hash__ = GenericEvent.__hash__ 154 | 155 | def __str__(self): 156 | return 'NoteOff %d at tick %d ch %d vel %d' % ( 157 | self.pitch, self.tick, self.channel, self.volume) 158 | 159 | def serialize(self, previous_event_tick): 160 | """Return a bytestring representation of the event, in the format required for 161 | writing into a standard midi file. 162 | """ 163 | midibytes = b"" 164 | code = self.midi_status | self.channel 165 | varTime = writeVarLength(self.tick - previous_event_tick) 166 | for timeByte in varTime: 167 | midibytes += struct.pack('>B', timeByte) 168 | midibytes += struct.pack('>B', code) 169 | midibytes += struct.pack('>B', self.pitch) 170 | midibytes += struct.pack('>B', self.volume) 171 | return midibytes 172 | 173 | 174 | class Tempo(GenericEvent): 175 | ''' 176 | A class that encapsulates a tempo meta-event 177 | ''' 178 | evtname = 'Tempo' 179 | sec_sort_order = 3 180 | 181 | def __init__(self, tick, tempo, insertion_order=0): 182 | self.tempo = int(60000000 / tempo) 183 | super(Tempo, self).__init__(tick, insertion_order) 184 | 185 | def __eq__(self, other): 186 | return (self.evtname == other.evtname and 187 | self.tick == other.tick and 188 | self.tempo == other.tempo) 189 | 190 | __hash__ = GenericEvent.__hash__ 191 | 192 | def serialize(self, previous_event_tick): 193 | """Return a bytestring representation of the event, in the format required for 194 | writing into a standard midi file. 195 | """ 196 | # Standard MIDI File Format says: 197 | # 198 | # FF 51 03 tttttt Set Tempo (in microseconds per MIDI quarter-note) 199 | # This event indicates a tempo change. Another way of putting 200 | # "microseconds per quarter-note" is "24ths of a microsecond per MIDI 201 | # clock". Representing tempos as time per beat instead of beat per time 202 | # allows absolutely exact long-term synchronisation with a time-based 203 | # sync protocol such as SMPTE time code or MIDI time code. The amount 204 | # of accuracy provided by this tempo resolution allows a four-minute 205 | # piece at 120 beats per minute to be accurate within 500 usec at the 206 | # end of the piece. Ideally, these events should only occur where MIDI 207 | # clocks would be located -- this convention is intended to guarantee, 208 | # or at least increase the likelihood, of compatibility with other 209 | # synchronisation devices so that a time signature/tempo map stored in 210 | # this format may easily be transferred to another device. 211 | # 212 | # Six identical lower-case letters such as tttttt refer to a 24-bit value, stored 213 | # most-significant-byte first. The notation len refers to the 214 | 215 | midibytes = b"" 216 | code = 0xFF 217 | subcode = 0x51 218 | fourbite = struct.pack('>L', self.tempo) # big-endian uint32 219 | threebite = fourbite[1:4] # Just discard the MSB 220 | varTime = writeVarLength(self.tick - previous_event_tick) 221 | for timeByte in varTime: 222 | midibytes += struct.pack('>B', timeByte) 223 | midibytes += struct.pack('>B', code) 224 | midibytes += struct.pack('>B', subcode) 225 | midibytes += struct.pack('>B', 0x03) # length in bytes of 24-bit tempo 226 | midibytes += threebite 227 | return midibytes 228 | 229 | 230 | class Copyright(GenericEvent): 231 | ''' 232 | A class that encapsulates a copyright event 233 | ''' 234 | evtname = 'Copyright' 235 | sec_sort_order = 1 236 | 237 | def __init__(self, tick, notice, insertion_order=0): 238 | self.notice = notice.encode("ISO-8859-1") 239 | super(Copyright, self).__init__(tick, insertion_order) 240 | 241 | def serialize(self, previous_event_tick): 242 | """Return a bytestring representation of the event, in the format required for 243 | writing into a standard midi file. 244 | """ 245 | # Standard MIDI File Format says: 246 | # 247 | # FF 02 len text Copyright Notice 248 | # Contains a copyright notice as printable ASCII text. The notice should 249 | # contain the characters (C), the year of the copyright, and the owner 250 | # of the copyright. If several pieces of music are in the same MIDI 251 | # File, all of the copyright notices should be placed together in this 252 | # event so that it will be at the beginning of the file. This event 253 | # should be the first event in the track chunk, at tick 0. 254 | midibytes = b"" 255 | code = 0xFF 256 | subcode = 0x02 257 | varTime = writeVarLength(self.tick - previous_event_tick) 258 | for timeByte in varTime: 259 | midibytes += struct.pack('>B', timeByte) 260 | midibytes += struct.pack('>B', code) 261 | midibytes += struct.pack('>B', subcode) 262 | payloadLength = len(self.notice) 263 | payloadLengthVar = writeVarLength(payloadLength) 264 | for i in payloadLengthVar: 265 | midibytes += struct.pack("b", i) 266 | midibytes += self.notice 267 | return midibytes 268 | 269 | 270 | class Text(GenericEvent): 271 | ''' 272 | A class that encapsulates a text event 273 | ''' 274 | evtname = 'Text' 275 | sec_sort_order = 1 276 | 277 | def __init__(self, tick, text, insertion_order=0): 278 | self.text = text.encode("ISO-8859-1") 279 | super(Text, self).__init__(tick, insertion_order) 280 | 281 | def serialize(self, previous_event_tick): 282 | """Return a bytestring representation of the event, in the format required for 283 | writing into a standard midi file. 284 | """ 285 | midibytes = b"" 286 | code = 0xFF 287 | subcode = 0x01 288 | varTime = writeVarLength(self.tick - previous_event_tick) 289 | for timeByte in varTime: 290 | midibytes += struct.pack('>B', timeByte) 291 | midibytes += struct.pack('>B', code) 292 | midibytes += struct.pack('>B', subcode) 293 | payloadLength = len(self.text) 294 | payloadLengthVar = writeVarLength(payloadLength) 295 | for i in payloadLengthVar: 296 | midibytes += struct.pack("B", i) 297 | midibytes += self.text 298 | return midibytes 299 | 300 | 301 | class KeySignature(GenericEvent): 302 | ''' 303 | A class that encapsulates a text event 304 | ''' 305 | evtname = 'KeySignature' 306 | sec_sort_order = 1 307 | 308 | def __init__(self, tick, accidentals, accidental_type, mode, 309 | insertion_order=0): 310 | self.accidentals = accidentals 311 | self.accidental_type = accidental_type 312 | self.mode = mode 313 | super(KeySignature, self).__init__(tick, insertion_order) 314 | 315 | def serialize(self, previous_event_tick): 316 | """Return a bytestring representation of the event, in the format required for 317 | writing into a standard midi file. 318 | """ 319 | midibytes = b"" 320 | code = 0xFF 321 | subcode = 0x59 322 | event_subtype = 0x02 323 | varTime = writeVarLength(self.tick - previous_event_tick) 324 | for timeByte in varTime: 325 | midibytes += struct.pack('>B', timeByte) 326 | midibytes += struct.pack('>B', code) 327 | midibytes += struct.pack('>B', subcode) 328 | midibytes += struct.pack('>B', event_subtype) 329 | midibytes += struct.pack('>b', self.accidentals * self.accidental_type) 330 | midibytes += struct.pack('>B', self.mode) 331 | return midibytes 332 | 333 | 334 | class ProgramChange(GenericEvent): 335 | ''' 336 | A class that encapsulates a program change event. 337 | ''' 338 | evtname = 'ProgramChange' 339 | midi_status = 0xc0 # 0xcx is Program Change 340 | sec_sort_order = 1 341 | 342 | def __init__(self, channel, tick, programNumber, 343 | insertion_order=0): 344 | self.programNumber = programNumber 345 | self.channel = channel 346 | super(ProgramChange, self).__init__(tick, insertion_order) 347 | 348 | def __eq__(self, other): 349 | return (self.evtname == other.evtname and 350 | self.tick == other.tick and 351 | self.programNumber == other.programNumber and 352 | self.channel == other.channel) 353 | 354 | __hash__ = GenericEvent.__hash__ 355 | 356 | def serialize(self, previous_event_tick): 357 | """Return a bytestring representation of the event, in the format required for 358 | writing into a standard midi file. 359 | """ 360 | midibytes = b"" 361 | code = self.midi_status | self.channel 362 | varTime = writeVarLength(self.tick) 363 | for timeByte in varTime: 364 | midibytes += struct.pack('>B', timeByte) 365 | midibytes += struct.pack('>B', code) 366 | midibytes += struct.pack('>B', self.programNumber) 367 | return midibytes 368 | 369 | 370 | class SysExEvent(GenericEvent): 371 | ''' 372 | A class that encapsulates a System Exclusive event. 373 | ''' 374 | evtname = 'SysEx' # doesn't match class name like most others 375 | sec_sort_order = 1 376 | 377 | def __init__(self, tick, manID, payload, insertion_order=0): 378 | self.manID = manID 379 | self.payload = payload 380 | super(SysExEvent, self).__init__(tick, insertion_order) 381 | 382 | def __eq__(self, other): 383 | return False 384 | 385 | __hash__ = GenericEvent.__hash__ 386 | 387 | def serialize(self, previous_event_tick): 388 | """Return a bytestring representation of the event, in the format required for 389 | writing into a standard midi file. 390 | """ 391 | midibytes = b"" 392 | code = 0xF0 393 | varTime = writeVarLength(self.tick - previous_event_tick) 394 | for timeByte in varTime: 395 | midibytes += struct.pack('>B', timeByte) 396 | midibytes += struct.pack('>B', code) 397 | 398 | payloadLength = writeVarLength(len(self.payload) + 2) 399 | for lenByte in payloadLength: 400 | midibytes += struct.pack('>B', lenByte) 401 | 402 | midibytes += struct.pack('>B', self.manID) 403 | midibytes += self.payload 404 | midibytes += struct.pack('>B', 0xF7) 405 | return midibytes 406 | 407 | 408 | class UniversalSysExEvent(GenericEvent): 409 | ''' 410 | A class that encapsulates a Universal System Exclusive event. 411 | ''' 412 | evtname = 'UniversalSysEx' # doesn't match class name like most others 413 | sec_sort_order = 1 414 | 415 | def __init__(self, tick, realTime, sysExChannel, code, subcode, 416 | payload, insertion_order=0): 417 | self.realTime = realTime 418 | self.sysExChannel = sysExChannel 419 | self.code = code 420 | self.subcode = subcode 421 | self.payload = payload 422 | super(UniversalSysExEvent, self).__init__(tick, insertion_order) 423 | 424 | def __eq__(self, other): 425 | return False 426 | 427 | __hash__ = GenericEvent.__hash__ 428 | 429 | def serialize(self, previous_event_tick): 430 | """Return a bytestring representation of the event, in the format required for 431 | writing into a standard midi file. 432 | """ 433 | midibytes = b"" 434 | code = 0xF0 435 | varTime = writeVarLength(self.tick - previous_event_tick) 436 | for timeByte in varTime: 437 | midibytes += struct.pack('>B', timeByte) 438 | midibytes += struct.pack('>B', code) 439 | 440 | # Do we need to add a length? 441 | payloadLength = writeVarLength(len(self.payload) + 5) 442 | for lenByte in payloadLength: 443 | midibytes += struct.pack('>B', lenByte) 444 | 445 | if self.realTime: 446 | midibytes += struct.pack('>B', 0x7F) 447 | else: 448 | midibytes += struct.pack('>B', 0x7E) 449 | 450 | midibytes += struct.pack('>B', self.sysExChannel) 451 | midibytes += struct.pack('>B', self.code) 452 | midibytes += struct.pack('>B', self.subcode) 453 | midibytes += self.payload 454 | midibytes += struct.pack('>B', 0xF7) 455 | return midibytes 456 | 457 | 458 | class ControllerEvent(GenericEvent): 459 | ''' 460 | A class that encapsulates a program change event. 461 | ''' 462 | evtname = 'ControllerEvent' 463 | midi_status = 0xB0 # 0xBx is Control Change 464 | sec_sort_order = 1 465 | 466 | def __init__(self, channel, tick, controller_number, parameter, 467 | insertion_order=0): 468 | self.parameter = parameter 469 | self.channel = channel 470 | self.controller_number = controller_number 471 | super(ControllerEvent, self).__init__(tick, insertion_order) 472 | 473 | def __eq__(self, other): 474 | return False 475 | 476 | __hash__ = GenericEvent.__hash__ 477 | 478 | def serialize(self, previous_event_tick): 479 | """Return a bytestring representation of the event, in the format required for 480 | writing into a standard midi file. 481 | """ 482 | midibytes = b"" 483 | code = self.midi_status | self.channel 484 | varTime = writeVarLength(self.tick - previous_event_tick) 485 | for timeByte in varTime: 486 | midibytes += struct.pack('>B', timeByte) 487 | midibytes += struct.pack('>B', code) 488 | midibytes += struct.pack('>B', self.controller_number) 489 | midibytes += struct.pack('>B', self.parameter) 490 | return midibytes 491 | 492 | 493 | class ChannelPressureEvent(GenericEvent): 494 | ''' 495 | A class that encapsulates a Channel Pressure (Aftertouch) event. 496 | ''' 497 | evtname = 'ChannelPressure' 498 | midi_status = 0xD0 # 0xDx is Channel Pressure (Aftertouch) 499 | sec_sort_order = 1 500 | 501 | def __init__(self, channel, tick, pressure_value, insertion_order=0): 502 | self.channel = channel 503 | self.pressure_value = pressure_value 504 | super(ChannelPressureEvent, self).__init__(tick, insertion_order) 505 | 506 | def __eq__(self, other): 507 | return (self.__class__.__name__ == other.__class__.__name__ and 508 | self.tick == other.tick and 509 | self.pressure_value == other.pressure_value and 510 | self.channel == other.channel) 511 | 512 | __hash__ = GenericEvent.__hash__ 513 | 514 | def serialize(self, previous_event_tick): 515 | """Return a bytestring representation of the event, in the format required for 516 | writing into a standard midi file. 517 | """ 518 | midibytes = b"" 519 | code = self.midi_status | self.channel 520 | vartick = writeVarLength(self.tick - previous_event_tick) 521 | for x in vartick: 522 | midibytes += struct.pack('>B', x) 523 | midibytes += struct.pack('>B', code) 524 | midibytes += struct.pack('>B', self.pressure_value) 525 | return midibytes 526 | 527 | 528 | class PitchWheelEvent(GenericEvent): 529 | ''' 530 | A class that encapsulates a pitch wheel change event. 531 | ''' 532 | evtname = 'PitchWheelEvent' 533 | midi_status = 0xE0 # 0xEx is Pitch Wheel Change 534 | sec_sort_order = 1 535 | 536 | def __init__(self, channel, tick, pitch_wheel_value, insertion_order=0): 537 | self.channel = channel 538 | self.pitch_wheel_value = pitch_wheel_value 539 | super(PitchWheelEvent, self).__init__(tick, insertion_order) 540 | 541 | def __eq__(self, other): 542 | return False 543 | 544 | __hash__ = GenericEvent.__hash__ 545 | 546 | def serialize(self, previous_event_tick): 547 | """Return a bytestring representation of the event, in the format required for 548 | writing into a standard midi file. 549 | """ 550 | midibytes = b"" 551 | code = self.midi_status | self.channel 552 | varTime = writeVarLength(self.tick - previous_event_tick) 553 | for timeByte in varTime: 554 | midibytes = midibytes + struct.pack('>B', timeByte) 555 | MSB = (self.pitch_wheel_value + 8192) >> 7 556 | LSB = (self.pitch_wheel_value + 8192) & 0x7F 557 | midibytes = midibytes + struct.pack('>B', code) 558 | midibytes = midibytes + struct.pack('>B', LSB) 559 | midibytes = midibytes + struct.pack('>B', MSB) 560 | return midibytes 561 | 562 | 563 | class TrackName(GenericEvent): 564 | ''' 565 | A class that encapsulates a program change event. 566 | ''' 567 | evtname = 'TrackName' 568 | sec_sort_order = 0 569 | 570 | def __init__(self, tick, trackName, insertion_order=0): 571 | # GenericEvent.__init__(self, tick) 572 | self.trackName = trackName.encode("ISO-8859-1") 573 | super(TrackName, self).__init__(tick, insertion_order) 574 | 575 | def __eq__(self, other): 576 | return (self.evtname == other.evtname and 577 | self.tick == other.tick and 578 | self.trackName == other.trackName) 579 | 580 | __hash__ = GenericEvent.__hash__ 581 | 582 | def serialize(self, previous_event_tick): 583 | """Return a bytestring representation of the event, in the format required for 584 | writing into a standard midi file. 585 | """ 586 | midibytes = b"" 587 | varTime = writeVarLength(self.tick - previous_event_tick) 588 | for timeByte in varTime: 589 | midibytes += struct.pack('>B', timeByte) 590 | midibytes += struct.pack('B', 0xFF) 591 | midibytes += struct.pack('B', 0X03) 592 | dataLength = len(self.trackName) 593 | dataLengthVar = writeVarLength(dataLength) 594 | for i in dataLengthVar: 595 | midibytes += struct.pack("B", i) 596 | midibytes += self.trackName 597 | return midibytes 598 | 599 | 600 | class TimeSignature(GenericEvent): 601 | ''' 602 | A class that encapsulates a time signature. 603 | ''' 604 | evtname = 'TimeSignature' 605 | sec_sort_order = 0 606 | 607 | def __init__(self, tick, numerator, denominator, clocks_per_tick, 608 | notes_per_quarter, insertion_order=0): 609 | self.numerator = numerator 610 | self.denominator = denominator 611 | self.clocks_per_tick = clocks_per_tick 612 | self.notes_per_quarter = notes_per_quarter 613 | super(TimeSignature, self).__init__(tick, insertion_order) 614 | 615 | def serialize(self, previous_event_tick): 616 | """Return a bytestring representation of the event, in the format required for 617 | writing into a standard midi file. 618 | """ 619 | midibytes = b"" 620 | code = 0xFF 621 | subcode = 0x58 622 | varTime = writeVarLength(self.tick - previous_event_tick) 623 | for timeByte in varTime: 624 | midibytes += struct.pack('>B', timeByte) 625 | midibytes += struct.pack('>B', code) 626 | midibytes += struct.pack('>B', subcode) 627 | midibytes += struct.pack('>B', 0x04) 628 | midibytes += struct.pack('>B', self.numerator) 629 | midibytes += struct.pack('>B', self.denominator) 630 | midibytes += struct.pack('>B', self.clocks_per_tick) 631 | # 32nd notes per quarter note 632 | midibytes += struct.pack('>B', self.notes_per_quarter) 633 | return midibytes 634 | 635 | 636 | class MIDITrack(object): 637 | ''' 638 | A class that encapsulates a MIDI track 639 | ''' 640 | 641 | def __init__(self, removeDuplicates, deinterleave): 642 | '''Initialize the MIDITrack object. 643 | ''' 644 | self.headerString = struct.pack('cccc', b'M', b'T', b'r', b'k') 645 | self.dataLength = 0 # Is calculated after the data is in place 646 | self.MIDIdata = b"" 647 | self.closed = False 648 | self.eventList = [] 649 | self.MIDIEventList = [] 650 | self.remdep = removeDuplicates 651 | self.deinterleave = deinterleave 652 | 653 | def addNoteByNumber(self, channel, pitch, tick, duration, volume, 654 | annotation=None, insertion_order=0): 655 | ''' 656 | Add a note by chromatic MIDI number 657 | ''' 658 | self.eventList.append(NoteOn(channel, pitch, tick, duration, volume, 659 | annotation=annotation, 660 | insertion_order=insertion_order)) 661 | 662 | # This event is not in chronological order. But before writing all the 663 | # events to the file, I sort self.eventlist on (tick, sec_sort_order, insertion_order) 664 | # which puts the events in chronological order. 665 | self.eventList.append(NoteOff(channel, pitch, tick + duration, volume, 666 | annotation=annotation, 667 | insertion_order=insertion_order)) 668 | 669 | def addControllerEvent(self, channel, tick, controller_number, parameter, 670 | insertion_order=0): 671 | ''' 672 | Add a controller event. 673 | ''' 674 | 675 | self.eventList.append(ControllerEvent(channel, tick, controller_number, 676 | parameter, 677 | insertion_order=insertion_order)) 678 | 679 | def addPitchWheelEvent(self, channel, tick, pitch_wheel_value, insertion_order=0): 680 | ''' 681 | Add a pitch wheel event. 682 | ''' 683 | self.eventList.append(PitchWheelEvent(channel, tick, pitch_wheel_value, insertion_order=insertion_order)) 684 | 685 | def addTempo(self, tick, tempo, insertion_order=0): 686 | ''' 687 | Add a tempo change (or set) event. 688 | ''' 689 | self.eventList.append(Tempo(tick, tempo, 690 | insertion_order=insertion_order)) 691 | 692 | def addSysEx(self, tick, manID, payload, insertion_order=0): 693 | ''' 694 | Add a SysEx event. 695 | ''' 696 | self.eventList.append(SysExEvent(tick, manID, payload, 697 | insertion_order=insertion_order)) 698 | 699 | def addUniversalSysEx(self, tick, code, subcode, payload, 700 | sysExChannel=0x7F, realTime=False, 701 | insertion_order=0): 702 | ''' 703 | Add a Universal SysEx event. 704 | ''' 705 | self.eventList.append(UniversalSysExEvent(tick, realTime, sysExChannel, 706 | code, subcode, payload, 707 | insertion_order=insertion_order)) 708 | 709 | def addProgramChange(self, channel, tick, program, insertion_order=0): 710 | ''' 711 | Add a program change event. 712 | ''' 713 | self.eventList.append(ProgramChange(channel, tick, program, 714 | insertion_order=insertion_order)) 715 | 716 | def addChannelPressure(self, channel, tick, pressure_value, insertion_order=0): 717 | ''' 718 | Add a channel pressure event. 719 | ''' 720 | self.eventList.append(ChannelPressureEvent(channel, tick, pressure_value, 721 | insertion_order=insertion_order)) 722 | 723 | def addTrackName(self, tick, trackName, insertion_order=0): 724 | ''' 725 | Add a track name event. 726 | ''' 727 | self.eventList.append(TrackName(tick, trackName, 728 | insertion_order=insertion_order)) 729 | 730 | def addTimeSignature(self, tick, numerator, denominator, clocks_per_tick, 731 | notes_per_quarter, insertion_order=0): 732 | ''' 733 | Add a time signature. 734 | ''' 735 | self.eventList.append(TimeSignature(tick, numerator, denominator, 736 | clocks_per_tick, notes_per_quarter, 737 | insertion_order=insertion_order)) 738 | 739 | def addCopyright(self, tick, notice, insertion_order=0): 740 | ''' 741 | Add a copyright notice 742 | ''' 743 | self.eventList.append(Copyright(tick, notice, 744 | insertion_order=insertion_order)) 745 | 746 | def addKeySignature(self, tick, accidentals, accidental_type, mode, 747 | insertion_order=0): 748 | ''' 749 | Add a copyright notice 750 | ''' 751 | self.eventList.append(KeySignature(tick, accidentals, accidental_type, 752 | mode, 753 | insertion_order=insertion_order)) 754 | 755 | def addText(self, tick, text, insertion_order=0): 756 | ''' 757 | Add a text event 758 | ''' 759 | self.eventList.append(Text(tick, text, 760 | insertion_order=insertion_order)) 761 | 762 | def changeNoteTuning(self, tunings, sysExChannel=0x7F, realTime=True, 763 | tuningProgam=0, insertion_order=0): 764 | ''' 765 | Change the tuning of MIDI notes 766 | ''' 767 | payload = struct.pack('>B', tuningProgam) 768 | payload = payload + struct.pack('>B', len(tunings)) 769 | for (noteNumber, frequency) in tunings: 770 | payload = payload + struct.pack('>B', noteNumber) 771 | MIDIFreqency = frequencyTransform(frequency) 772 | for byte in MIDIFreqency: 773 | payload = payload + struct.pack('>B', byte) 774 | 775 | self.eventList.append(UniversalSysExEvent(0, realTime, sysExChannel, 776 | 8, 2, payload, insertion_order=insertion_order)) 777 | 778 | def processEventList(self): 779 | ''' 780 | Process the event list, creating a MIDIEventList, 781 | which is then sorted to be in chronological order by start tick. 782 | ''' 783 | 784 | self.MIDIEventList = [evt for evt in self.eventList] 785 | # Assumptions in the code expect the list to be time-sorted. 786 | self.MIDIEventList.sort(key=sort_events) 787 | 788 | if self.deinterleave: 789 | self.deInterleaveNotes() 790 | 791 | def removeDuplicates(self): 792 | ''' 793 | Remove duplicates from the eventList. 794 | 795 | This function will remove duplicates from the eventList. This is 796 | necessary because we the MIDI event stream can become confused 797 | otherwise. 798 | ''' 799 | 800 | # For this algorithm to work, the events in the eventList must be 801 | # hashable (that is, they must have a __hash__() and __eq__() function 802 | # defined). 803 | 804 | s = set(self.eventList) 805 | self.eventList = list(s) 806 | self.eventList.sort(key=sort_events) 807 | 808 | def closeTrack(self): 809 | ''' 810 | Called to close a track before writing 811 | 812 | This function should be called to "close a track," that is to 813 | prepare the actual data stream for writing. Duplicate events are 814 | removed from the eventList, and the MIDIEventList is created. 815 | 816 | Called by the parent MIDIFile object. 817 | ''' 818 | 819 | if self.closed: 820 | return 821 | self.closed = True 822 | 823 | if self.remdep: 824 | self.removeDuplicates() 825 | 826 | self.processEventList() 827 | 828 | def writeMIDIStream(self): 829 | ''' 830 | Write the meta data and note data to the packed MIDI stream. 831 | ''' 832 | 833 | # Process the events in the eventList 834 | 835 | self.writeEventsToStream() 836 | 837 | # Write MIDI close event. 838 | 839 | self.MIDIdata += struct.pack('BBBB', 0x00, 0xFF, 0x2F, 0x00) 840 | 841 | # Calculate the entire length of the data and write to the header 842 | 843 | self.dataLength = struct.pack('>L', len(self.MIDIdata)) 844 | 845 | def writeEventsToStream(self): 846 | ''' 847 | Write the events in MIDIEvents to the MIDI stream. 848 | MIDIEventList is presumed to be already sorted in chronological order. 849 | ''' 850 | previous_event_tick = 0 851 | for event in self.MIDIEventList: 852 | self.MIDIdata += event.serialize(previous_event_tick) 853 | # previous_event_tick = event.tick 854 | # I do not like that adjustTimeAndOrigin() changes GenericEvent.tick 855 | # from absolute to relative. I intend to change that, and just 856 | # calculate the relative tick here, without changing GenericEvent.tick 857 | 858 | def deInterleaveNotes(self): 859 | ''' 860 | Correct Interleaved notes. 861 | 862 | Because we are writing multiple notes in no particular order, we 863 | can have notes which are interleaved with respect to their start 864 | and stop times. This method will correct that. It expects that the 865 | MIDIEventList has been time-ordered. 866 | ''' 867 | 868 | tempEventList = [] 869 | stack = {} 870 | 871 | for event in self.MIDIEventList: 872 | if event.evtname in ['NoteOn', 'NoteOff']: 873 | # !!! Pitch 101 channel 5 produces the same key as pitch 10 channel 15. 874 | # !!! This is not the only pair of pitch,channel tuples which 875 | # !!! collide to the same key, just one example. Should fix by 876 | # !!! putting a separator char between pitch and channel. 877 | noteeventkey = str(event.pitch) + str(event.channel) 878 | if event.evtname == 'NoteOn': 879 | if noteeventkey in stack: 880 | stack[noteeventkey].append(event.tick) 881 | else: 882 | stack[noteeventkey] = [event.tick] 883 | tempEventList.append(event) 884 | elif event.evtname == 'NoteOff': 885 | if len(stack[noteeventkey]) > 1: 886 | event.tick = stack[noteeventkey].pop() 887 | tempEventList.append(event) 888 | else: 889 | stack[noteeventkey].pop() 890 | tempEventList.append(event) 891 | else: 892 | tempEventList.append(event) 893 | 894 | self.MIDIEventList = tempEventList 895 | 896 | # Note NoteOff events have a lower secondary sort key than NoteOn 897 | # events, so this sort will make concomitant NoteOff events 898 | # processed first. 899 | 900 | self.MIDIEventList.sort(key=sort_events) 901 | 902 | def adjustTimeAndOrigin(self, origin, adjust): 903 | ''' 904 | Adjust Times to be relative, and zero-origined. 905 | 906 | If adjust is True, the track will be shifted. Regardelss times 907 | are converted to relative values here. 908 | ''' 909 | 910 | if len(self.MIDIEventList) == 0: 911 | return 912 | tempEventList = [] 913 | internal_origin = origin if adjust else 0 914 | runningTick = 0 915 | 916 | for event in self.MIDIEventList: 917 | adjustedTick = event.tick - internal_origin 918 | event.tick = adjustedTick - runningTick 919 | runningTick = adjustedTick 920 | tempEventList.append(event) 921 | 922 | self.MIDIEventList = tempEventList 923 | 924 | def writeTrack(self, fileHandle): 925 | ''' 926 | Write track to disk. 927 | ''' 928 | 929 | fileHandle.write(self.headerString) 930 | fileHandle.write(self.dataLength) 931 | fileHandle.write(self.MIDIdata) 932 | 933 | 934 | class MIDIHeader(object): 935 | ''' 936 | Class to encapsulate the MIDI header structure. 937 | 938 | This class encapsulates a MIDI header structure. It isn't used for much, 939 | but it will create the appropriately packed identifier string that all 940 | MIDI files should contain. It is used by the MIDIFile class to create a 941 | complete and well formed MIDI pattern. 942 | 943 | ''' 944 | def __init__(self, numTracks, file_format, ticks_per_quarternote): 945 | ''' Initialize the data structures 946 | 947 | :param numTracks: The number of tracks the file contains. Integer, 948 | one or greater 949 | :param file_format: The format of the multi-track file. This should 950 | either be ``1`` (the default, and the most widely supported 951 | format) or ``2``. 952 | :param ticks_per_quarternote: The number of ticks per quarter 953 | note is what the Standard MIDI File Format Specification calls 954 | "division". Ticks are the integer unit of time in the SMF, and in 955 | every MIDI sequencer I am aware of. Common values are 120, 240, 956 | 384, 480, 960. Note that all these numbers are evenly divisible by 957 | 2,3,4,6,8,12,16, and 24, except 120 does not have 16 as a divisor. 958 | ''' 959 | self.headerString = struct.pack('cccc', b'M', b'T', b'h', b'd') 960 | self.headerSize = struct.pack('>L', 6) 961 | # Format 1 = multi-track file 962 | self.formatnum = struct.pack('>H', file_format) 963 | self.numeric_format = file_format 964 | self.numTracks = struct.pack('>H', numTracks) 965 | self.ticks_per_quarternote = struct.pack('>H', ticks_per_quarternote) 966 | 967 | def writeFile(self, fileHandle): 968 | fileHandle.write(self.headerString) 969 | fileHandle.write(self.headerSize) 970 | fileHandle.write(self.formatnum) 971 | fileHandle.write(self.numTracks) 972 | fileHandle.write(self.ticks_per_quarternote) 973 | 974 | 975 | class MIDIFile(object): 976 | ''' 977 | A class that encapsulates a full, well-formed MIDI file object. 978 | 979 | This is a container object that contains a header (:class:`MIDIHeader`), 980 | one or more tracks (class:`MIDITrack`), and the data associated with a 981 | proper and well-formed MIDI file. 982 | ''' 983 | 984 | def __init__(self, numTracks=1, removeDuplicates=True, deinterleave=True, 985 | adjust_origin=False, file_format=1, 986 | ticks_per_quarternote=TICKSPERQUARTERNOTE, eventtime_is_ticks=False): 987 | '''Initialize the MIDIFile class 988 | 989 | :param numTracks: The number of tracks the file contains. Integer, 990 | one or greater 991 | :param removeDuplicates: If set to ``True`` remove duplicate events 992 | before writing to disk 993 | :param deinterleave: If set to ``True`` deinterleave the notes in 994 | the stream 995 | :param adjust_origin: If set to ``True`` shift all the events in the tracks 996 | so that the first event takes place at time t=0. Default is ``False`` 997 | :param file_format: The format of the multi-track file. This should 998 | either be ``1`` (the default, and the most widely supported 999 | format) or ``2``. 1000 | :param ticks_per_quarternote: The number of ticks per quarter note is 1001 | what the Standard MIDI File Format Specification calls "division". 1002 | Ticks are the integer unit of time in the SMF, and in most if 1003 | not all MIDI sequencers. Common values are 120, 240, 384, 1004 | 480, 960. Note that all these numbers are evenly divisible by 1005 | 2,3,4,6,8,12,16, and 24, except 120 does not have 16 as a divisor. 1006 | 1007 | :param eventtime_is_ticks: If set True means event time and duration 1008 | argument values are integer ticks instead of fractional quarter 1009 | notes. 1010 | 1011 | Note that the default for ``adjust_origin`` will change in a future 1012 | release, so one should probably explicitly set it. 1013 | 1014 | In a format 1 file, it would be a rare cirumstance where adjusting the 1015 | origin of each track to the track's first note makes any sense. 1016 | 1017 | Example: 1018 | 1019 | .. code:: 1020 | 1021 | # Create a two-track MIDIFile 1022 | 1023 | from midiutil.MidiFile import MIDIFile 1024 | midi_file = MIDIFile(1, adjust_origin=False) 1025 | 1026 | A Note on File Formats 1027 | ---------------------- 1028 | 1029 | In previous versions of this code the file written was format 2 1030 | (which can be thought of as a collection of independent tracks) but 1031 | was identified as format 1. In this version one can specify either 1032 | format 1 or 2. 1033 | 1034 | In format 1 files there is a separate tempo track which contains 1035 | tempo and time signature data, but contains no note data. If one 1036 | creates a single track format 1 file the actual file has two tracks 1037 | -- one for tempo data and one for note data. In the track indexing 1038 | the tempo track can be ignored. In other words track 0 is the note 1039 | track (the second track in the file). However, tempo and time 1040 | signature data will be written to the first, tempo track. This is 1041 | done to try and preserve as much interoperability with previous 1042 | versions as possible. 1043 | 1044 | In a format 2 file all tracks are indexed and the track parameter 1045 | is interpreted literally. 1046 | ''' 1047 | 1048 | self.tracks = list() 1049 | if file_format == 1: 1050 | self.numTracks = numTracks + 1 # self.tracks[0] is the baked-in tempo track 1051 | else: 1052 | self.numTracks = numTracks 1053 | self.header = MIDIHeader(self.numTracks, file_format, ticks_per_quarternote) 1054 | 1055 | self.adjust_origin = adjust_origin 1056 | self.closed = False 1057 | 1058 | self.ticks_per_quarternote = ticks_per_quarternote 1059 | self.eventtime_is_ticks = eventtime_is_ticks 1060 | if self.eventtime_is_ticks: 1061 | self.time_to_ticks = lambda x: x 1062 | else: 1063 | self.time_to_ticks = self.quarter_to_tick 1064 | 1065 | for i in range(0, self.numTracks): 1066 | self.tracks.append(MIDITrack(removeDuplicates, deinterleave)) 1067 | # to keep track of the order of insertion for new sorting 1068 | self.event_counter = 0 1069 | 1070 | # Public Functions. These (for the most part) wrap the MIDITrack functions, 1071 | # where most Processing takes place. 1072 | 1073 | def quarter_to_tick(self, quarternote_time): 1074 | return int(quarternote_time * self.ticks_per_quarternote) 1075 | 1076 | def tick_to_quarter(self, ticknum): 1077 | return float(ticknum) / self.ticks_per_quarternote 1078 | 1079 | def addNote(self, track, channel, pitch, time, duration, volume, 1080 | annotation=None): 1081 | """ 1082 | 1083 | Add notes to the MIDIFile object 1084 | 1085 | :param track: The track to which the note is added. 1086 | :param channel: the MIDI channel to assign to the note. [Integer, 0-15] 1087 | :param pitch: the MIDI pitch number [Integer, 0-127]. 1088 | :param time: the time at which the note sounds. The value can be either 1089 | quarter notes [Float], or ticks [Integer]. Ticks may be specified by 1090 | passing eventtime_is_ticks=True to the MIDIFile constructor. 1091 | The default is quarter notes. 1092 | :param duration: the duration of the note. Like the time argument, the 1093 | value can be either quarter notes [Float], or ticks [Integer]. 1094 | :param volume: the volume (velocity) of the note. [Integer, 0-127]. 1095 | :param annotation: Arbitrary data to attach to the note. 1096 | 1097 | The ``annotation`` parameter attaches arbitrary data to the note. This 1098 | is not used in the code, but can be useful anyway. As an example, 1099 | I have created a project that uses MIDIFile to write 1100 | `csound `_ orchestra files directly from the 1101 | class ``EventList``. 1102 | """ 1103 | if self.header.numeric_format == 1: 1104 | track += 1 1105 | self.tracks[track].addNoteByNumber(channel, pitch, 1106 | self.time_to_ticks(time), self.time_to_ticks(duration), 1107 | volume, annotation=annotation, 1108 | insertion_order=self.event_counter) 1109 | self.event_counter += 1 1110 | 1111 | def addTrackName(self, track, time, trackName): 1112 | """ 1113 | Name a track. 1114 | 1115 | :param track: The track to which the name is assigned. 1116 | :param time: The time (in beats) at which the track name event is 1117 | placed. In general this should probably be time 0 (the beginning 1118 | of the track). 1119 | :param trackName: The name to assign to the track [String] 1120 | """ 1121 | if self.header.numeric_format == 1: 1122 | track += 1 1123 | self.tracks[track].addTrackName(self.time_to_ticks(time), trackName, 1124 | insertion_order=self.event_counter) 1125 | self.event_counter += 1 1126 | 1127 | def addTimeSignature(self, track, time, numerator, denominator, 1128 | clocks_per_tick, notes_per_quarter=8): 1129 | ''' 1130 | Add a time signature event. 1131 | 1132 | :param track: The track to which the signature is assigned. Note that 1133 | in a format 1 file this parameter is ignored and the event is 1134 | written to the tempo track 1135 | :param time: The time (in beats) at which the event is placed. 1136 | In general this should probably be time 0 (the beginning of the 1137 | track). 1138 | :param numerator: The numerator of the time signature. [Int] 1139 | :param denominator: The denominator of the time signature, expressed as 1140 | a power of two (see below). [Int] 1141 | :param clocks_per_tick: The number of MIDI clock ticks per metronome 1142 | click (see below). 1143 | :param notes_per_quarter: The number of annotated 32nd notes in a MIDI 1144 | quarter note. This is almost always 8 (the default), but some 1145 | sequencers allow this value to be changed. Unless you know that 1146 | your sequencing software supports it, this should be left at its 1147 | default value. 1148 | 1149 | The data format for this event is a little obscure. 1150 | 1151 | The ``denominator`` should be specified as a power of 2, with 1152 | a half note being one, a quarter note being two, and eight note 1153 | being three, etc. Thus, for example, a 4/4 time signature would 1154 | have a ``numerator`` of 4 and a ``denominator`` of 2. A 7/8 time 1155 | signature would be a ``numerator`` of 7 and a ``denominator`` 1156 | of 3. 1157 | 1158 | The ``clocks_per_tick`` argument specifies the number of clock 1159 | ticks per metronome click. By definition there are 24 ticks in 1160 | a quarter note, so a metronome click per quarter note would be 1161 | 24. A click every third eighth note would be 3 * 12 = 36. 1162 | 1163 | The ``notes_per_quarter`` value is also a little confusing. It 1164 | specifies the number of 32nd notes in a MIDI quarter note. Usually 1165 | there are 8 32nd notes in a quarter note (8/32 = 1/4), so 1166 | the default value is 8. However, one can change this value if 1167 | needed. Setting it to 16, for example, would cause the music to 1168 | play at double speed, as there would be 16/32 (or what could be 1169 | considered *two* quarter notes for every one MIDI quarter note. 1170 | 1171 | Note that both the ``clocks_per_tick`` and the 1172 | ``notes_per_quarter`` are specified in terms of quarter notes, 1173 | even is the score is not a quarter-note based score (i.e., 1174 | even if the denominator is not ``4``). So if you're working with a 1175 | time signature of, say, 6/8, one still needs to specify the clocks 1176 | per quarter note. 1177 | ''' 1178 | if self.header.numeric_format == 1: 1179 | track = 0 1180 | 1181 | self.tracks[track].addTimeSignature(self.time_to_ticks(time), numerator, denominator, 1182 | clocks_per_tick, notes_per_quarter, 1183 | insertion_order=self.event_counter) 1184 | self.event_counter += 1 1185 | 1186 | def addTempo(self, track, time, tempo): 1187 | """ 1188 | 1189 | Add notes to the MIDIFile object 1190 | 1191 | :param track: The track to which the tempo event is added. Note that 1192 | in a format 1 file this parameter is ignored and the tempo is 1193 | written to the tempo track 1194 | :param time: The time (in beats) at which tempo event is placed 1195 | :param tempo: The tempo, in Beats per Minute. [Integer] 1196 | """ 1197 | if self.header.numeric_format == 1: 1198 | track = 0 1199 | self.tracks[track].addTempo(self.time_to_ticks(time), tempo, 1200 | insertion_order=self.event_counter) 1201 | self.event_counter += 1 1202 | 1203 | def addCopyright(self, track, time, notice): 1204 | """ 1205 | 1206 | Add a copyright notice to the MIDIFile object 1207 | 1208 | :param track: The track to which the notice is added. 1209 | :param time: The time (in beats) at which notice event is placed. In 1210 | general this sould be time t=0 1211 | :param notice: The copyright notice [String] 1212 | """ 1213 | if self.header.numeric_format == 1: 1214 | track += 1 1215 | self.tracks[track].addCopyright(self.time_to_ticks(time), notice, 1216 | insertion_order=self.event_counter) 1217 | self.event_counter += 1 1218 | 1219 | def addKeySignature(self, track, time, accidentals, accidental_type, mode, 1220 | insertion_order=0): 1221 | ''' 1222 | Add a Key Signature to a track 1223 | 1224 | :param track: The track to which this should be added 1225 | :param time: The time at which the signature should be placed 1226 | :param accidentals: The number of accidentals in the key signature 1227 | :param accidental_type: The type of accidental 1228 | :param mode: The mode of the scale 1229 | 1230 | The easiest way to use this function is to make sure that the symbolic 1231 | constants for accidental_type and mode are imported. By doing this: 1232 | 1233 | .. code:: 1234 | 1235 | from midiutil.MidiFile import * 1236 | 1237 | one gets the following constants defined: 1238 | 1239 | * ``SHARPS`` 1240 | * ``FLATS`` 1241 | * ``MAJOR`` 1242 | * ``MINOR`` 1243 | 1244 | So, for example, if one wanted to create a key signature for a minor 1245 | scale with three sharps: 1246 | 1247 | .. code:: 1248 | 1249 | MyMIDI.addKeySignature(0, 0, 3, SHARPS, MINOR) 1250 | ''' 1251 | if self.header.numeric_format == 1: 1252 | track = 0 # User reported that this is needed. 1253 | self.tracks[track].addKeySignature(self.time_to_ticks(time), accidentals, accidental_type, 1254 | mode, insertion_order=self.event_counter) 1255 | self.event_counter += 1 1256 | 1257 | def addText(self, track, time, text): 1258 | """ 1259 | 1260 | Add a text event 1261 | 1262 | :param track: The track to which the notice is added. 1263 | :param time: The time (in beats) at which text event is placed. 1264 | :param text: The text to adde [ASCII String] 1265 | """ 1266 | if self.header.numeric_format == 1: 1267 | track += 1 1268 | self.tracks[track].addText(self.time_to_ticks(time), text, 1269 | insertion_order=self.event_counter) 1270 | self.event_counter += 1 1271 | 1272 | def addProgramChange(self, tracknum, channel, time, program): 1273 | """ 1274 | 1275 | Add a MIDI program change event. 1276 | 1277 | :param tracknum: The zero-based track number to which program change event is added. 1278 | :param channel: the MIDI channel to assign to the event. 1279 | [Integer, 0-15] 1280 | :param time: The time (in beats) at which the program change event is 1281 | placed [Float]. 1282 | :param program: the program number. [Integer, 0-127]. 1283 | """ 1284 | if self.header.numeric_format == 1: 1285 | tracknum += 1 1286 | self.tracks[tracknum].addProgramChange(channel, self.time_to_ticks(time), program, 1287 | insertion_order=self.event_counter) 1288 | self.event_counter += 1 1289 | 1290 | def addChannelPressure(self, tracknum, channel, time, pressure_value): 1291 | """ 1292 | Add a Channel Pressure event. 1293 | 1294 | :param tracknum: The zero-based track number to which channel pressure event is added. 1295 | :param channel: the MIDI channel to assign to the event. 1296 | [Integer, 0-15] 1297 | :param time: The time (in beats) at which the channel pressure event is 1298 | placed [Float]. 1299 | :param pressure_value: the pressure value. [Integer, 0-127]. 1300 | """ 1301 | if self.header.numeric_format == 1: 1302 | tracknum += 1 1303 | track = self.tracks[tracknum] 1304 | track.addChannelPressure(channel, self.time_to_ticks(time), pressure_value, 1305 | insertion_order=self.event_counter) 1306 | self.event_counter += 1 1307 | 1308 | def addControllerEvent(self, track, channel, time, controller_number, 1309 | parameter): 1310 | """ 1311 | 1312 | Add a channel control event 1313 | 1314 | :param track: The track to which the event is added. 1315 | :param channel: the MIDI channel to assign to the event. 1316 | [Integer, 0-15] 1317 | :param time: The time (in beats) at which the event is placed [Float]. 1318 | :param controller_number: The controller ID of the event. 1319 | :param parameter: The event's parameter, the meaning of which varies by 1320 | event type. 1321 | """ 1322 | if self.header.numeric_format == 1: 1323 | track += 1 1324 | self.tracks[track].addControllerEvent(channel, self.time_to_ticks(time), controller_number, 1325 | parameter, insertion_order=self.event_counter) # noqa: E128 1326 | self.event_counter += 1 1327 | 1328 | def addPitchWheelEvent(self, track, channel, time, pitchWheelValue): 1329 | """ 1330 | 1331 | Add a channel pitch wheel event 1332 | 1333 | :param track: The track to which the event is added. 1334 | :param channel: the MIDI channel to assign to the event. [Integer, 0-15] 1335 | :param time: The time (in beats) at which the event is placed [Float]. 1336 | :param pitchWheelValue: 0 for no pitch change. [Integer, -8192-8192] 1337 | """ 1338 | if self.header.numeric_format == 1: 1339 | track += 1 1340 | self.tracks[track].addPitchWheelEvent(channel, self.time_to_ticks(time), pitchWheelValue, 1341 | insertion_order=self.event_counter) 1342 | self.event_counter += 1 1343 | 1344 | def makeRPNCall(self, track, channel, time, controller_msb, controller_lsb, 1345 | data_msb, data_lsb, time_order=False): 1346 | ''' 1347 | 1348 | Perform a Registered Parameter Number Call 1349 | 1350 | :param track: The track to which this applies 1351 | :param channel: The channel to which this applies 1352 | :param time: The time of the event 1353 | :param controller_msb: The Most significant byte of the controller. In 1354 | common usage this will usually be 0 1355 | :param controller_lsb: The Least significant Byte for the controller 1356 | message. For example, for a fine-tuning change this would be 01. 1357 | :param data_msb: The Most Significant Byte of the controller's 1358 | parameter. 1359 | :param data_lsb: The Least Significant Byte of the controller's 1360 | parameter. If not needed this should be set to ``None`` 1361 | :param time_order: Order the control events in time (see below) 1362 | 1363 | As an example, if one were to change a channel's tuning program:: 1364 | 1365 | makeRPNCall(track, channel, time, 0, 3, 0, program) 1366 | 1367 | (Note, however, that there is a convenience function, 1368 | ``changeTuningProgram``, that does this for you.) 1369 | 1370 | Registered/Non-Registered Parameter Number (RPN / NRPN) 1371 | ------------------------------------------------------- 1372 | Controller number 6 (Data Entry), in conjunction with Controller numbers 96 1373 | (Data Increment), 97 (Data Decrement), 98 (Registered Parameter Number LSB), 1374 | 99 (Registered Parameter Number MSB), 100 (Non-Registered Parameter Number 1375 | LSB), and 101 (Non-Registered Parameter Number MSB), extend the number of 1376 | controllers available via MIDI. Parameter data is transferred by first 1377 | selecting the parameter number to be edited using controllers 98 and 99 or 1378 | 100 and 101, and then adjusting the data value for that parameter using 1379 | controller number 6, 96, or 97. 1380 | 1381 | RPN and NRPN are typically used to send parameter data to a synthesizer in 1382 | order to edit sound patches or other data. Registered parameters are those 1383 | which have been assigned some particular function by the MIDI Manufacturers 1384 | Association (MMA) and the Japan MIDI Standards Committee (JMSC). For 1385 | example, there are Registered Parameter numbers assigned to control pitch 1386 | bend sensitivity and master tuning for a synthesizer. Non-Registered 1387 | parameters have not been assigned specific functions, and may be used for 1388 | different functions by different manufacturers. 1389 | 1390 | The ``time_order`` parameter is something of a work-around for 1391 | sequencers that do not preserve the order of events from the MIDI files 1392 | they import. Within this code care is taken to preserve the order of 1393 | events as specified, but some sequencers seem to transmit events 1394 | occurring at the same time in an arbitrary order. By setting this 1395 | parameter to ``True`` something of a work-around is performed: each 1396 | successive event (of which there are three or four for this event type) 1397 | is placed in the time stream a small delta from the preceding one. 1398 | Thus, for example, the controllers are set before the data bytes in 1399 | this call. 1400 | ''' 1401 | tick = self.time_to_ticks(time) 1402 | 1403 | if self.header.numeric_format == 1: 1404 | track += 1 1405 | track = self.tracks[track] 1406 | 1407 | tick_incr = 1 if time_order else 0 1408 | track.addControllerEvent(channel, tick, 101, # parameter number MSB 1409 | controller_msb, insertion_order=self.event_counter) # noqa: E128 1410 | self.event_counter += 1 1411 | tick += tick_incr 1412 | track.addControllerEvent(channel, tick, 100, 1413 | controller_lsb, insertion_order=self.event_counter) # noqa: E128 1414 | self.event_counter += 1 1415 | tick += tick_incr 1416 | track.addControllerEvent(channel, tick, 6, 1417 | data_msb, insertion_order=self.event_counter) # noqa: E128 1418 | self.event_counter += 1 1419 | tick += tick_incr 1420 | if data_lsb is not None: 1421 | track.addControllerEvent(channel, tick, 38, 1422 | data_lsb, insertion_order=self.event_counter) # noqa: E128 1423 | self.event_counter += 1 1424 | 1425 | def makeNRPNCall(self, track, channel, time, controller_msb, 1426 | controller_lsb, data_msb, data_lsb, time_order=False): 1427 | ''' 1428 | 1429 | Perform a Non-Registered Parameter Number Call 1430 | 1431 | :param track: The track to which this applies 1432 | :param channel: The channel to which this applies 1433 | :param time: The time of the event 1434 | :param controller_msb: The Most significant byte of thecontroller. In 1435 | common usage this will usually be 0 1436 | :param controller_lsb: The least significant byte for the controller 1437 | message. For example, for a fine-tunning change this would be 01. 1438 | :param data_msb: The most significant byte of the controller's 1439 | parameter. 1440 | :param data_lsb: The least significant byte of the controller's 1441 | parameter. If none is needed this should be set to ``None`` 1442 | :param time_order: Order the control events in time (see below) 1443 | 1444 | The ``time_order`` parameter is something of a work-around for 1445 | sequencers that do not preserve the order of events from the MIDI files 1446 | they import. Within this code care is taken to preserve the order of 1447 | events as specified, but some sequencers seem to transmit events 1448 | occurring at the same time in an arbitrary order. By setting this 1449 | parameter to ``True`` something of a work-around is performed: each 1450 | successive event (of which there are three or four for this event type) 1451 | is placed in the time stream a small delta from the preceding one. 1452 | Thus, for example, the controllers are set before the data bytes in 1453 | this call. 1454 | 1455 | ''' 1456 | tick = self.time_to_ticks(time) 1457 | 1458 | if self.header.numeric_format == 1: 1459 | track += 1 1460 | track = self.tracks[track] 1461 | 1462 | tick_incr = 1 if time_order else 0 1463 | track.addControllerEvent(channel, tick, 99, 1464 | controller_msb, insertion_order=self.event_counter) # noqa: E128 1465 | self.event_counter += 1 1466 | tick += tick_incr 1467 | track.addControllerEvent(channel, tick, 98, 1468 | controller_lsb, insertion_order=self.event_counter) # noqa: E128 1469 | self.event_counter += 1 1470 | tick += tick_incr 1471 | track.addControllerEvent(channel, tick, 6, 1472 | data_msb, insertion_order=self.event_counter) # noqa: E128 1473 | self.event_counter += 1 1474 | tick += tick_incr 1475 | if data_lsb is not None: 1476 | track.addControllerEvent(channel, tick, 38, 1477 | data_lsb, insertion_order=self.event_counter) # noqa: E128 1478 | self.event_counter += 1 1479 | 1480 | def changeTuningBank(self, track, channel, time, bank, time_order=False): 1481 | ''' 1482 | 1483 | Change the tuning bank for a selected track 1484 | 1485 | :param track: The track to which the data should be written 1486 | :param channel: The channel for the event 1487 | :param time: The time of the event 1488 | :param bank: The tuning bank (0-127) 1489 | :param time_order: Preserve the ordering of the component events by 1490 | ordering in time. See ``makeRPNCall()`` for a discussion of when 1491 | this may be necessary 1492 | 1493 | Note that this is a convenience function, as the same 1494 | functionality is available from directly sequencing controller 1495 | events. 1496 | 1497 | The specified tuning should already have been written to the 1498 | stream with ``changeNoteTuning``. ''' 1499 | self.makeRPNCall(track, channel, time, 0, 4, 0, bank, 1500 | time_order=time_order) 1501 | 1502 | def changeTuningProgram(self, track, channel, time, program, 1503 | time_order=False): 1504 | ''' 1505 | 1506 | Change the tuning program for a selected track 1507 | 1508 | :param track: The track to which the data should be written 1509 | :param channel: The channel for the event 1510 | :param time: The time of the event 1511 | :param program: The tuning program number (0-127) 1512 | :param time_order: Preserve the ordering of the component events by 1513 | ordering in time. See ``makeRPNCall()`` for a discussion of when 1514 | this may be necessary 1515 | 1516 | Note that this is a convenience function, as the same 1517 | functionality is available from directly sequencing controller 1518 | events. 1519 | 1520 | The specified tuning should already have been written to the 1521 | stream with ``changeNoteTuning``. 1522 | ''' 1523 | self.makeRPNCall(track, channel, time, 0, 3, 0, program, 1524 | time_order=time_order) 1525 | 1526 | def changeNoteTuning(self, track, tunings, sysExChannel=0x7F, 1527 | realTime=True, tuningProgam=0): 1528 | """ 1529 | Add a real-time MIDI tuning standard update to a track. 1530 | 1531 | :param track: The track to which the tuning is applied. 1532 | :param tunings: A list to tuples representing the tuning. See below for 1533 | an explanation. 1534 | :param sysExChannel: The SysEx channel of the event. This is mapped to 1535 | "manufacturer ID" in the event which is written. Unless there is a 1536 | specific reason for changing it, it should be left at its default 1537 | value. 1538 | :param realTime: Speicifes if the Universal SysEx event should be 1539 | flagged as real-time or non-real-time. As with the ``sysExChannel`` 1540 | argument, this should in general be left at it's default value. 1541 | :param tuningProgram: The tuning program number. 1542 | 1543 | This function specifically implements the "real time single note tuning 1544 | change" (although the name is misleading, as multiple notes can be 1545 | included in each event). It should be noted that not all hardware or 1546 | software implements the MIDI tuning standard, and that which does often 1547 | does not implement it in its entirety. 1548 | 1549 | The ``tunings`` argument is a list of tuples, in (*note number*, 1550 | *frequency*) format. As an example, if one wanted to change the 1551 | frequency on MIDI note 69 to 500 (it is normally 440 Hz), one could do 1552 | it thus: 1553 | 1554 | .. code:: python 1555 | 1556 | from midiutil.MidiFile import MIDIFile 1557 | MyMIDI = MIDIFile(1) 1558 | tuning = [(69, 500)] 1559 | MyMIDI.changeNoteTuning(0, tuning, tuningProgam=0) 1560 | """ 1561 | if self.header.numeric_format == 1: 1562 | track += 1 1563 | self.tracks[track].changeNoteTuning(tunings, sysExChannel, realTime, 1564 | tuningProgam, 1565 | insertion_order=self.event_counter) 1566 | self.event_counter += 1 1567 | 1568 | def addSysEx(self, track, time, manID, payload): 1569 | ''' 1570 | 1571 | Add a System Exclusive event. 1572 | 1573 | :param track: The track to which the event should be written 1574 | :param time: The time of the event. 1575 | :param manID: The manufacturer ID for the event 1576 | :param payload: The payload for the event. This should be a 1577 | binary-packed value, and will vary for each type and function. 1578 | 1579 | **Note**: This is a low-level MIDI function, so care must be used in 1580 | constructing the payload. It is recommended that higher-level helper 1581 | functions be written to wrap this function and construct the payload if 1582 | a developer finds him or herself using the function heavily. 1583 | 1584 | ''' 1585 | tick = self.time_to_ticks(time) 1586 | if self.header.numeric_format == 1: 1587 | track += 1 1588 | self.tracks[track].addSysEx(tick, manID, payload, 1589 | insertion_order=self.event_counter) 1590 | self.event_counter += 1 1591 | 1592 | def addUniversalSysEx(self, track, time, code, subcode, payload, 1593 | sysExChannel=0x7F, realTime=False): 1594 | ''' 1595 | 1596 | Add a Univeral System Exclusive event. 1597 | 1598 | :param track: The track to which the event should be written 1599 | :param time: The time of the event, in beats. 1600 | :param code: The event code. [Integer] 1601 | :param subcode: The event sub-code [Integer] 1602 | :param payload: The payload for the event. This should be a 1603 | binary-packed value, and will vary for each type and function. 1604 | :param sysExChannel: The SysEx channel. 1605 | :param realTime: Sets the real-time flag. Defaults to non-real-time. 1606 | :param manID: The manufacturer ID for the event 1607 | 1608 | 1609 | **Note**: This is a low-level MIDI function, so care must be used in 1610 | constructing the payload. It is recommended that higher-level helper 1611 | functions be written to wrap this function and construct the payload if 1612 | a developer finds him or herself using the function heavily. As an 1613 | example of such a helper function, see the ``changeNoteTuning()`` 1614 | function, which uses the event to create a real-time note tuning 1615 | update. 1616 | 1617 | ''' 1618 | tick = self.time_to_ticks(time) 1619 | if self.header.numeric_format == 1: 1620 | track += 1 1621 | self.tracks[track].addUniversalSysEx(tick, code, subcode, payload, 1622 | sysExChannel, realTime, 1623 | insertion_order=self.event_counter) # noqa: E128 1624 | self.event_counter += 1 1625 | 1626 | def writeFile(self, fileHandle): 1627 | ''' 1628 | Write the MIDI File. 1629 | 1630 | :param fileHandle: A file handle that has been opened for binary 1631 | writing. 1632 | ''' 1633 | 1634 | self.header.writeFile(fileHandle) 1635 | 1636 | # Close the tracks and have them create the MIDI event data structures. 1637 | self.close() 1638 | 1639 | # Write the MIDI Events to file. 1640 | for i in range(0, self.numTracks): 1641 | self.tracks[i].writeTrack(fileHandle) 1642 | 1643 | def shiftTracks(self, offset=0): 1644 | """Shift tracks to be zero-origined, or origined at offset. 1645 | 1646 | Note that the shifting of the time in the tracks uses the MIDIEventList 1647 | -- in other words it is assumed to be called in the stage where the 1648 | MIDIEventList has been created. This function, however, it meant to 1649 | operate on the eventList itself. 1650 | """ 1651 | origin = 100000000 # A little silly, but we'll assume big enough 1652 | tick_offset = self.time_to_ticks(offset) 1653 | 1654 | for track in self.tracks: 1655 | if len(track.eventList) > 0: 1656 | for event in track.eventList: 1657 | if event.tick < origin: 1658 | origin = event.tick 1659 | 1660 | for track in self.tracks: 1661 | tempEventList = [] 1662 | # runningTick = 0 1663 | 1664 | for event in track.eventList: 1665 | adjustedTick = event.tick - origin 1666 | # event.time = adjustedTime - runningTick + tick_offset 1667 | event.tick = adjustedTick + tick_offset 1668 | # runningTick = adjustedTick 1669 | tempEventList.append(event) 1670 | 1671 | track.eventList = tempEventList 1672 | 1673 | # End Public Functions ######################## 1674 | 1675 | def close(self): 1676 | ''' 1677 | Close the MIDIFile for further writing. 1678 | 1679 | To close the File for events, we must close the tracks, adjust the time 1680 | to be zero-origined, and have the tracks write to their MIDI Stream 1681 | data structure. 1682 | ''' 1683 | 1684 | if self.closed: 1685 | return 1686 | 1687 | for i in range(0, self.numTracks): 1688 | self.tracks[i].closeTrack() 1689 | # We want things like program changes to come before notes when 1690 | # they are at the same time, so we sort the MIDI events by both 1691 | # their start time and a secondary ordinality defined for each kind 1692 | # of event. 1693 | self.tracks[i].MIDIEventList.sort(key=sort_events) 1694 | 1695 | origin = self.findOrigin() 1696 | 1697 | for i in range(0, self.numTracks): 1698 | self.tracks[i].adjustTimeAndOrigin(origin, self.adjust_origin) 1699 | self.tracks[i].writeMIDIStream() 1700 | 1701 | self.closed = True 1702 | 1703 | def findOrigin(self): 1704 | ''' 1705 | Find the earliest time in the file's tracks.append. 1706 | ''' 1707 | origin = 100000000 # A little silly, but we'll assume big enough 1708 | 1709 | # Note: This code assumes that the MIDIEventList has been sorted, so this 1710 | # should be insured before it is called. It is probably a poor design to do 1711 | # this. 1712 | # TODO: -- Consider making this less efficient but more robust by not 1713 | # assuming the list to be sorted. 1714 | 1715 | for track in self.tracks: 1716 | if len(track.MIDIEventList) > 0: 1717 | if track.MIDIEventList[0].tick < origin: 1718 | origin = track.MIDIEventList[0].tick 1719 | 1720 | return origin 1721 | 1722 | 1723 | def writeVarLength(i): 1724 | ''' 1725 | Accept an integer, and serialize it as a MIDI file variable length quantity 1726 | 1727 | Some numbers in MTrk chunks are represented in a form called a variable- 1728 | length quantity. These numbers are represented in a sequence of bytes, 1729 | each byte holding seven bits of the number, and ordered most significant 1730 | bits first. All bytes in the sequence except the last have bit 7 set, 1731 | and the last byte has bit 7 clear. This form allows smaller numbers to 1732 | be stored in fewer bytes. For example, if the number is between 0 and 1733 | 127, it is thus represented exactly as one byte. A number between 128 1734 | and 16383 uses two bytes, and so on. 1735 | 1736 | Examples: 1737 | Number VLQ 1738 | 128 81 00 1739 | 8192 C0 00 1740 | 16383 FF 7F 1741 | 16384 81 80 00 1742 | ''' 1743 | if i == 0: 1744 | return [0] 1745 | 1746 | vlbytes = [] 1747 | hibit = 0x00 # low-order byte has high bit cleared. 1748 | while i > 0: 1749 | vlbytes.append(((i & 0x7f) | hibit) & 0xff) 1750 | i >>= 7 1751 | hibit = 0x80 1752 | vlbytes.reverse() # put most-significant byte first, least significant last 1753 | return vlbytes 1754 | 1755 | 1756 | # readVarLength is taken from the MidiFile class. 1757 | 1758 | def readVarLength(offset, buffer): 1759 | ''' 1760 | A function to read a MIDI variable length variable. 1761 | 1762 | It returns a tuple of the value read and the number of bytes processed. The 1763 | input is an offset into the buffer, and the buffer itself. 1764 | ''' 1765 | toffset = offset 1766 | output = 0 1767 | bytesRead = 0 1768 | while True: 1769 | output = output << 7 1770 | byte = struct.unpack_from('>B', buffer, toffset)[0] 1771 | toffset = toffset + 1 1772 | bytesRead = bytesRead + 1 1773 | output = output + (byte & 127) 1774 | if (byte & 128) == 0: 1775 | break 1776 | return (output, bytesRead) 1777 | 1778 | 1779 | def frequencyTransform(freq): 1780 | ''' 1781 | Returns a three-byte transform of a frequency. 1782 | ''' 1783 | resolution = 16384 1784 | freq = float(freq) 1785 | dollars = 69 + 12 * math.log(freq / (float(440)), 2) 1786 | firstByte = int(dollars) 1787 | lowerFreq = 440 * pow(2.0, ((float(firstByte) - 69.0) / 12.0)) 1788 | centDif = 1200 * math.log((freq / lowerFreq), 2) if freq != lowerFreq else 0 1789 | cents = round(centDif / 100 * resolution) # round? 1790 | secondByte = min([int(cents) >> 7, 0x7F]) 1791 | thirdByte = cents - (secondByte << 7) 1792 | thirdByte = min([thirdByte, 0x7f]) 1793 | if thirdByte == 0x7f and secondByte == 0x7F and firstByte == 0x7F: 1794 | thirdByte = 0x7e 1795 | thirdByte = int(thirdByte) 1796 | return [firstByte, secondByte, thirdByte] 1797 | 1798 | 1799 | def returnFrequency(freqBytes): 1800 | ''' 1801 | The reverse of frequencyTransform. Given a byte stream, return a frequency. 1802 | ''' 1803 | resolution = 16384.0 1804 | baseFrequency = 440 * pow(2.0, (float(freqBytes[0] - 69.0) / 12.0)) 1805 | frac = (float((int(freqBytes[1]) << 7) + int(freqBytes[2])) * 100.0) / resolution 1806 | frequency = baseFrequency * pow(2.0, frac / 1200.0) 1807 | return frequency 1808 | 1809 | 1810 | def sort_events(event): 1811 | ''' 1812 | .. py:function:: sort_events(event) 1813 | 1814 | The key function used to sort events (both MIDI and Generic) 1815 | 1816 | :param event: An object of type :class:`MIDIEvent` or (a derrivative) 1817 | :class:`GenericEvent` 1818 | 1819 | This function should be provided as the ``key`` for both 1820 | ``list.sort()`` and ``sorted()``. By using it sorting will be as 1821 | follows: 1822 | 1823 | * Events are ordered in time. An event that takes place earlier will 1824 | appear earlier 1825 | * If two events happen at the same time, the secondary sort key is 1826 | ``sec_sort_order``. Thus a class of events can be processed earlier 1827 | than another. One place this is used in the code is to make sure that 1828 | note off events are processed before note on events. 1829 | * If event time and event ordinality are the same, they are sorted in 1830 | the order in which they were originally added to the list. Thus, for 1831 | example, if one is making an RPN call one can specify the controller 1832 | change events in the proper order and be sure that they will end up in 1833 | the file that way. 1834 | ''' 1835 | 1836 | return (event.tick, event.sec_sort_order, event.insertion_order) 1837 | -------------------------------------------------------------------------------- /midiutil/__init__.py: -------------------------------------------------------------------------------- 1 | from midiutil.MidiFile import * 2 | 3 | __all__ = ['MIDIFile', 'MAJOR', 'MINOR', 'SHARPS', 'FLATS'] 4 | -------------------------------------------------------------------------------- /pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | 3 | # Specify a configuration file. 4 | #rcfile= 5 | 6 | # Python code to execute, usually for sys.path manipulation such as 7 | # pygtk.require(). 8 | #init-hook= 9 | 10 | # Profiled execution. 11 | profile=no 12 | 13 | # Add files or directories to the blacklist. They should be base names, not 14 | # paths. 15 | ignore=CVS 16 | 17 | # Pickle collected data for later comparisons. 18 | persistent=yes 19 | 20 | # List of plugins (as comma separated values of python modules names) to load, 21 | # usually to register additional checkers. 22 | load-plugins= 23 | 24 | # Allow loading of arbitrary C extensions. Extensions are imported into the 25 | # active Python interpreter and may run arbitrary code. 26 | unsafe-load-any-extension=no 27 | 28 | # A comma-separated list of package or module names from where C extensions may 29 | # be loaded. Extensions are loading into the active Python interpreter and may 30 | # run arbitrary code 31 | extension-pkg-whitelist= 32 | 33 | 34 | [REPORTS] 35 | 36 | # Set the output format. Available formats are text, parseable, colorized, msvs 37 | # (visual studio) and html. You can also give a reporter class, eg 38 | # mypackage.mymodule.MyReporterClass. 39 | output-format=text 40 | 41 | # Put messages in a separate file for each module / package specified on the 42 | # command line instead of printing them on stdout. Reports (if any) will be 43 | # written in a file name "pylint_global.[txt|html]". 44 | files-output=no 45 | 46 | # Tells whether to display a full report or only the messages 47 | reports=yes 48 | 49 | # Python expression which should return a note less than 10 (10 is the highest 50 | # note). You have access to the variables errors warning, statement which 51 | # respectively contain the number of errors / warnings messages and the total 52 | # number of statements analyzed. This is used by the global evaluation report 53 | # (RP0004). 54 | evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) 55 | 56 | # Add a comment according to your evaluation note. This is used by the global 57 | # evaluation report (RP0004). 58 | comment=no 59 | 60 | # Template used to display messages. This is a python new-style format string 61 | # used to format the message information. See doc for all details 62 | #msg-template= 63 | 64 | 65 | [MESSAGES CONTROL] 66 | 67 | # Enable the message, report, category or checker with the given id(s). You can 68 | # either give multiple identifier separated by comma (,) or put this option 69 | # multiple time. See also the "--disable" option for examples. 70 | #enable= 71 | 72 | # Disable the message, report, category or checker with the given id(s). You 73 | # can either give multiple identifiers separated by comma (,) or put this 74 | # option multiple times (only on the command line, not in the configuration 75 | # file where it should appear only once).You can also use "--disable=all" to 76 | # disable everything first and then reenable specific checks. For example, if 77 | # you want to run only the similarities checker, you can use "--disable=all 78 | # --enable=similarities". If you want to run only the classes checker, but have 79 | # no Warning level messages displayed, use"--disable=all --enable=classes 80 | # --disable=W" 81 | disable=I0011,C0302 82 | 83 | 84 | [VARIABLES] 85 | 86 | # Tells whether we should check for unused import in __init__ files. 87 | init-import=no 88 | 89 | # A regular expression matching the name of dummy variables (i.e. expectedly 90 | # not used). 91 | dummy-variables-rgx=_$|dummy 92 | 93 | # List of additional names supposed to be defined in builtins. Remember that 94 | # you should avoid to define new builtins when possible. 95 | additional-builtins= 96 | 97 | 98 | [LOGGING] 99 | 100 | # Logging modules to check that the string format arguments are in logging 101 | # function parameter format 102 | logging-modules=logging 103 | 104 | 105 | [MISCELLANEOUS] 106 | 107 | # List of note tags to take in consideration, separated by a comma. 108 | notes=FIXME,XXX,TODO 109 | 110 | 111 | [FORMAT] 112 | 113 | # Maximum number of characters on a single line. 114 | max-line-length=80 115 | 116 | # Regexp for a line that is allowed to be longer than the limit. 117 | ignore-long-lines=^\s*(# )??$ 118 | 119 | # Allow the body of an if to be on the same line as the test if there is no 120 | # else. 121 | single-line-if-stmt=no 122 | 123 | # List of optional constructs for which whitespace checking is disabled 124 | no-space-check=trailing-comma,dict-separator 125 | 126 | # Maximum number of lines in a module 127 | max-module-lines=1000 128 | 129 | # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 130 | # tab). 131 | indent-string=' ' 132 | 133 | # Number of spaces of indent required inside a hanging or continued line. 134 | indent-after-paren=4 135 | 136 | 137 | [TYPECHECK] 138 | 139 | # Tells whether missing members accessed in mixin class should be ignored. A 140 | # mixin class is detected if its name ends with "mixin" (case insensitive). 141 | ignore-mixin-members=yes 142 | 143 | # List of module names for which member attributes should not be checked 144 | # (useful for modules/projects where namespaces are manipulated during runtime 145 | # and thus existing member attributes cannot be deduced by static analysis 146 | ignored-modules= 147 | 148 | # List of classes names for which member attributes should not be checked 149 | # (useful for classes with attributes dynamically set). 150 | ignored-classes=SQLObject 151 | 152 | # When zope mode is activated, add a predefined set of Zope acquired attributes 153 | # to generated-members. 154 | zope=no 155 | 156 | # List of members which are set dynamically and missed by pylint inference 157 | # system, and so shouldn't trigger E0201 when accessed. Python regular 158 | # expressions are accepted. 159 | generated-members=REQUEST,acl_users,aq_parent 160 | 161 | 162 | [BASIC] 163 | 164 | # Required attributes for module, separated by a comma 165 | required-attributes= 166 | 167 | # List of builtins function names that should not be used, separated by a comma 168 | bad-functions=map,filter,apply 169 | 170 | # Good variable names which should always be accepted, separated by a comma 171 | good-names=i,j,k,ex,Run,_ 172 | 173 | # Bad variable names which should always be refused, separated by a comma 174 | bad-names=foo,bar,baz,toto,tutu,tata 175 | 176 | # Colon-delimited sets of names that determine each other's naming style when 177 | # the name regexes allow several styles. 178 | name-group= 179 | 180 | # Include a hint for the correct naming format with invalid-name 181 | include-naming-hint=no 182 | 183 | # Regular expression matching correct class attribute names 184 | class-attribute-rgx=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 185 | 186 | # Naming hint for class attribute names 187 | class-attribute-name-hint=([A-Za-z_][A-Za-z0-9_]{2,30}|(__.*__))$ 188 | 189 | # Regular expression matching correct function names 190 | function-rgx=[a-z_][a-z0-9_]{2,30}$ 191 | 192 | # Naming hint for function names 193 | function-name-hint=[a-z_][a-z0-9_]{2,30}$ 194 | 195 | # Regular expression matching correct variable names 196 | variable-rgx=[a-z_][a-z0-9_]{2,30}$ 197 | 198 | # Naming hint for variable names 199 | variable-name-hint=[a-z_][a-z0-9_]{2,30}$ 200 | 201 | # Regular expression matching correct inline iteration names 202 | inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$ 203 | 204 | # Naming hint for inline iteration names 205 | inlinevar-name-hint=[A-Za-z_][A-Za-z0-9_]*$ 206 | 207 | # Regular expression matching correct class names 208 | class-rgx=[A-Z_][a-zA-Z0-9]+$ 209 | 210 | # Naming hint for class names 211 | class-name-hint=[A-Z_][a-zA-Z0-9]+$ 212 | 213 | # Regular expression matching correct module names 214 | module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 215 | 216 | # Naming hint for module names 217 | module-name-hint=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$ 218 | 219 | # Regular expression matching correct argument names 220 | argument-rgx=[a-z_][a-z0-9_]{2,30}$ 221 | 222 | # Naming hint for argument names 223 | argument-name-hint=[a-z_][a-z0-9_]{2,30}$ 224 | 225 | # Regular expression matching correct method names 226 | method-rgx=[a-z_][a-z0-9_]{2,30}$ 227 | 228 | # Naming hint for method names 229 | method-name-hint=[a-z_][a-z0-9_]{2,30}$ 230 | 231 | # Regular expression matching correct constant names 232 | const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 233 | 234 | # Naming hint for constant names 235 | const-name-hint=(([A-Z_][A-Z0-9_]*)|(__.*__))$ 236 | 237 | # Regular expression matching correct attribute names 238 | attr-rgx=[a-z_][a-z0-9_]{2,30}$ 239 | 240 | # Naming hint for attribute names 241 | attr-name-hint=[a-z_][a-z0-9_]{2,30}$ 242 | 243 | # Regular expression which should only match function or class names that do 244 | # not require a docstring. 245 | no-docstring-rgx=__.*__ 246 | 247 | # Minimum line length for functions/classes that require docstrings, shorter 248 | # ones are exempt. 249 | docstring-min-length=-1 250 | 251 | 252 | [SIMILARITIES] 253 | 254 | # Minimum lines number of a similarity. 255 | min-similarity-lines=4 256 | 257 | # Ignore comments when computing similarities. 258 | ignore-comments=yes 259 | 260 | # Ignore docstrings when computing similarities. 261 | ignore-docstrings=yes 262 | 263 | # Ignore imports when computing similarities. 264 | ignore-imports=no 265 | 266 | 267 | [DESIGN] 268 | 269 | # Maximum number of arguments for function / method 270 | max-args=5 271 | 272 | # Argument names that match this expression will be ignored. Default to name 273 | # with leading underscore 274 | ignored-argument-names=_.* 275 | 276 | # Maximum number of locals for function / method body 277 | max-locals=15 278 | 279 | # Maximum number of return / yield for function / method body 280 | max-returns=6 281 | 282 | # Maximum number of branch for function / method body 283 | max-branches=12 284 | 285 | # Maximum number of statements in function / method body 286 | max-statements=50 287 | 288 | # Maximum number of parents for a class (see R0901). 289 | max-parents=7 290 | 291 | # Maximum number of attributes for a class (see R0902). 292 | max-attributes=7 293 | 294 | # Minimum number of public methods for a class (see R0903). 295 | min-public-methods=2 296 | 297 | # Maximum number of public methods for a class (see R0904). 298 | max-public-methods=20 299 | 300 | 301 | [CLASSES] 302 | 303 | # List of interface methods to ignore, separated by a comma. This is used for 304 | # instance to not check methods defines in Zope's Interface base class. 305 | ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by 306 | 307 | # List of method names used to declare (i.e. assign) instance attributes. 308 | defining-attr-methods=__init__,__new__,setUp 309 | 310 | # List of valid names for the first argument in a class method. 311 | valid-classmethod-first-arg=cls 312 | 313 | # List of valid names for the first argument in a metaclass class method. 314 | valid-metaclass-classmethod-first-arg=mcs 315 | 316 | 317 | [IMPORTS] 318 | 319 | # Deprecated modules which should not be used, separated by a comma 320 | deprecated-modules=stringprep,optparse 321 | 322 | # Create a graph of every (i.e. internal and external) dependencies in the 323 | # given file (report RP0402 must not be disabled) 324 | import-graph= 325 | 326 | # Create a graph of external dependencies in the given file (report RP0402 must 327 | # not be disabled) 328 | ext-import-graph= 329 | 330 | # Create a graph of internal dependencies in the given file (report RP0402 must 331 | # not be disabled) 332 | int-import-graph= 333 | 334 | 335 | [EXCEPTIONS] 336 | 337 | # Exceptions that will emit a warning when being caught. Defaults to 338 | # "Exception" 339 | overgeneral-exceptions=Exception 340 | -------------------------------------------------------------------------------- /python.com: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jperon/gabctk/40b58b627e24a8561c421c3b495646eb87a2559e/python.com -------------------------------------------------------------------------------- /windows/compilationsource/ReadMeGabc2mid.txt: -------------------------------------------------------------------------------- 1 | Wednesday, June 26, 2013 2 | gabc2mid.exe 3 | by Father Jacques Peron 4 | https://github.com/jperon/gabc2mid 5 | 6 | This little applicaion will create a midi file from a gabc score. 7 | 8 | ------------------------------------------------------------------- 9 | DESCRIPTION 10 | gabc is the language used by the program called Gregorio, an application designed to render code as Gregorian Chant. 11 | 12 | Gregorio Home page: http://home.gna.org/gregorio/ 13 | Description of gabc: http://home.gna.org/gregorio/gabc/ 14 | 15 | This application extracts the code from the gabc file, reads the melody and produces a midi file from the results. 16 | 17 | For more information, please see the accompanying gabc2mid.chm help file. 18 | 19 | ------------------------------------------------------------------- 20 | LICENSE: 21 | By using this program you thereby agree to the terms of this license agreement. You use this application at your own risk, and assume the entire responsibility of it use - that means that if it does something dastardly to your computer, it is your own fault. However, to reassure you, know that the authour has tested it and uses it himself. 22 | 23 | This application may be used for any use private or commercial, and without any fees. You may distribute it however you like as long as you don't charge for it, and as long as it remains unchanged, and is accompanied by this help file. 24 | 25 | ------------------------------------------------------------------- 26 | CONTACT: 27 | If you have ideas for improvements, or want to report a bug, please use the github interface: 28 | https://github.com/jperon/gabc2mid/issues 29 | or you may post something to the gregorio mailing list: 30 | https://mail.gna.org/listinfo/gregorio-users -------------------------------------------------------------------------------- /windows/compilationsource/help/gabc2mid.files/img1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jperon/gabctk/40b58b627e24a8561c421c3b495646eb87a2559e/windows/compilationsource/help/gabc2mid.files/img1.jpg -------------------------------------------------------------------------------- /windows/compilationsource/help/gabc2mid.hhp: -------------------------------------------------------------------------------- 1 | [OPTIONS] 2 | Title=gabc2mid 3 | Compiled file=..\gabc2mid.chm 4 | Error log file= 5 | Default topic=gabc2mid.htm 6 | Language=0x0000 7 | Full text search stop list file= 8 | Contents file= 9 | Index file= 10 | Binary TOC=No 11 | Auto index=No 12 | Binary Index=No 13 | Create CHI file=No 14 | Full-text search=No 15 | Display compile progress=Yes 16 | Display compile notes=Yes 17 | Default window=basic 18 | Enhanced decompilation=No 19 | Flat=No 20 | Compatibility=1.1 or later 21 | 22 | [WINDOWS] 23 | basic="",,,,,,,,,0x00042100,264,0x0010387E,[25,25,517,537],,,,0,-1,0,0 24 | 25 | [FILES] 26 | gabc2mid.htm 27 | 28 | -------------------------------------------------------------------------------- /windows/compilationsource/help/gabc2mid.htm: -------------------------------------------------------------------------------- 1 | 2 | gabc2mid.htm 3 | 4 | 198 | 199 | 200 |

gabc2mid

201 |


202 |


203 |

204 |

gabc2mid.exe

205 |

version 1.3

206 |

by Father Jacques Peron

207 |

Windows binary and English Documentation by Brother Gabriel-Marie

208 |


209 |

You can download the latest of the Windows binary here

210 |


211 |

This little applicaion will create a midi file from a gabc score.

212 |


213 |

Contents

214 | 223 |


224 |
225 |

226 | Description

227 |

gabc is the language used by the program called Gregorio, a tex-based application designed to render code as Gregorian Chant.

228 |


229 |

Gregorio Home page: http://home.gna.org/gregorio/

230 |

Description of gabc: http://home.gna.org/gregorio/gabc/

231 |


232 |

This application extracts the code from the gabc file, reads the melody and produces a midi file from the results.

233 |


234 |

If you prefer a GUI for use with gabc2mid, be sure to check out greg.

235 |
236 |

237 | Usage

238 |

gabc2mid.exe -i [filename] [-o filename] [-t tempo] [-e filename] [-v] [-d]

239 |


240 |

Note that if you are using the original python script, you won't use gabc2mid.exe - you would use gabc2mid.py.

241 |

If you have spaces in your filenames, make sure to enclose them in double-quotes.

242 |


243 |

Examples:

244 |

gabc2mid.exe -i path/to/source.gabc -o path/to/destination.midi -t tempo -e path/to/exportfile.txt

245 |

gabc2mid.exe -i c:/scores/myscore.gabc -o "c:/my scores/midi/mymidi.midi"

246 |


247 |

or, simply:

248 |

gabc2mid.exe [path/to/source.gabc]

249 |


250 |

You may specify only a single parameter being the path to the gabc file, in which case, a midi file will be produced in the same directory and using the same base name.

251 |


252 |

253 | Parameters        

254 |
255 | 256 | 258 | 260 | 261 | 262 | 264 | 266 | 267 | 268 | 270 | 272 | 273 | 274 | 276 | 278 | 279 | 280 | 282 | 284 | 285 | 286 | 288 | 290 | 291 |

-i [file.gabc]

257 |

input - this is the filename of the gabc file to input

259 |

-o [file.midi]

263 |

output - this is the filename of the midi file to output

265 |

-t [value]

269 |

tempo - default is 165.  This value (temps premier) is the number of notes that can be sounded in one minute.  In other words, during one minute, by default, the midi file will play 165 notes (not adjusted for episemas or other pauses and breaks; the value is arbitrary and was chosen because it sounded right - there isn't any mathematical reason, really, in case you were wondering) - which is about 1/3 of a second.

271 |

-d [value]

275 |

transposition - the number of halftones (positive or negative) to add to the original score.  If this is omitted, gabc2mid detects the highest and lowest note, calculates their middle, and transposes the chant so that this middle is on F#4.

277 |

-e [file.ext]

281 |

export - exports the text portion of the gabc code to a text file (you can specify any extension you like)

283 |

-v

287 |

verbose - prints debug messages in the console, but may not work properly due to Windows' poor handling of UTF8 in the console shell.

289 |
292 |
293 |


294 |


295 |
296 |


297 |

298 | License

299 |

By using this program you thereby agree to the terms of this license agreement.  You use this application at your own risk, and assume the entire responsibility of it use - that means that if it does something dastardly to your computer, it is your own fault.  However, to reassure you, know that the authour has tested it and uses it himself.

300 |


301 |

This application may be used for any use private or commercial, and without any fees.  You may distribute it however you like as long as it is accompanied by this help file.

302 |


303 |
304 |

305 | History

306 |


307 |

version 1.3        Thursday, July 4, 2013

308 |
    309 |
  • added transposition parameter
  • 310 |
311 |


312 |

version 1.2        Tuesday, July 2, 2013

313 |
    314 |
  • changed debug parameter to -v
  • 315 |
  • added help source to git
  • 316 |
317 |


318 |

version 1.1        Sunday, 30 June 2013

319 |
    320 |
  • added parameter for export to text file
  • 321 |
  • added parameter for showing debug remarks
  • 322 |
  • added chm help file
  • 323 |
324 |


325 |

version 1.0        Sunday, 30 June 2013

326 |
    327 |
  • initial release
  • 328 |
329 |


330 |
331 |


332 |

333 | Contact & Info

334 |

You can download the latest of the Windows binary here

335 |

This documentation is available online too.

336 |


337 |

If you have ideas for improvements, or want to report a bug, please use the github interface:

338 |

https://github.com/jperon/gabc2mid/issues

339 |


340 |

or you may post something to the gregorio mailing list:

341 |

https://mail.gna.org/listinfo/gregorio-users

342 |


343 |

home page for gabc2mid

344 |

https://github.com/jperon/gabc2mid

345 |


346 |


347 | 348 | -------------------------------------------------------------------------------- /windows/compilationsource/help/readme.txt: -------------------------------------------------------------------------------- 1 | Tuesday, July 02, 2013 2 | 3 | If you are looking for a chm file, it is packaged with the windows binary. 4 | 5 | -BGM -------------------------------------------------------------------------------- /windows/compilationsource/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jperon/gabctk/40b58b627e24a8561c421c3b495646eb87a2559e/windows/compilationsource/icon.ico -------------------------------------------------------------------------------- /windows/compilationsource/versioninfo.py: -------------------------------------------------------------------------------- 1 | VSVersionInfo( 2 | ffi=FixedFileInfo( 3 | filevers=(0, 0, 1, 3), 4 | prodvers=(0, 0, 1, 3), 5 | mask=0x3f, 6 | flags=0x0, 7 | OS=0x40004, 8 | fileType=0x1, 9 | subtype=0x0, 10 | date=(0, 0) 11 | ), 12 | kids=[ 13 | StringFileInfo( 14 | [ 15 | StringTable( 16 | u'040904B0', 17 | [StringStruct(u'CompanyName', u'SSPX by Father Jaques Peron'), 18 | StringStruct(u'FileDescription', u'gabc2mid'), 19 | StringStruct(u'FileVersion', u'0.0.1.3'), 20 | StringStruct(u'InternalName', u'gabc2mid'), 21 | StringStruct(u'LegalCopyright', u'\xa9 SSPX All rights reserved.'), 22 | StringStruct(u'OriginalFilename', u'gabc2mid.exe'), 23 | StringStruct(u'ProductName', u'gabc2mid'), 24 | StringStruct(u'ProductVersion', u'0.0.1.3')]) 25 | ]), 26 | VarFileInfo([VarStruct(u'Translation', [1033, 1200])]) 27 | ] 28 | ) -------------------------------------------------------------------------------- /windows/gabc2mid.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jperon/gabctk/40b58b627e24a8561c421c3b495646eb87a2559e/windows/gabc2mid.zip --------------------------------------------------------------------------------