📜 ⬆️ ⬇️

We type in LilyPond using midi-keyboard

I already wrote a couple of times about lilypond, and now I bought a midi-keyboard.

Many music editors, including Finale and Sibelius, have the ability to type notes from the midi-keyboard as many as two ways: either you can play something under the metronome, and this will be immediately recorded with notes, or you can enter only notes, and rhythm and everything else is introduced in the usual way.

I decided that a similar opportunity would not hurt for my preferred lilypond. Since the ability to record a midi-file and then convert it with midi2ly does not suit me - too much information of exactly the type of information in the midi-file cannot be reflected (we already discussed this ) - I decided to write a program so that the keys and chords are immediately converted to the required format.
')
UPD: Notebook is needed for about half of the following

The set in lilypond using the \ relative command allows you not to specify an octave for each note, but to indicate only the direction of the octave change. Without such instructions, each next note (in the case of a chord, the sound indicated first) turns out to be no further than a quarter. That is, the fa after d will be scored higher, even if the fa is sharp, and the d - flat (twice enlarged, but a quart, yes).

Writing the program set before me the difficulty of two kinds: the actual work with the midi-keyboard and the conversion of the received signals into the desired form.

midi-dot-net: working with MIDI


First of all, I downloaded the midi-dot-net library. It provides the possibility of both input and output, but we are now interested in input.

List of devices, opening and closing

The list of available input devices, I shoved it into the combo box.

using Midi; // ....... private void LoadMidiDevices() { foreach (InputDevice d in InputDevice.InstalledDevices) { DeviceList.Items.Add(d.Name); } DeviceList.SelectedIndex = 0; UpdateDevice(); } 


In addition to the device name, you can also find out the ManufacturerId, ProductId and all this together in one field Spec.

The methods Open () and Close () open and close the device, the state can be obtained from the fields IsOpen, StartReceiving (), StopReceiving (), IsReceiving, respectively, are responsible for receiving information. The library provides a convenient binding of events in the style of C #.

The StartReceiving () method optionally accepts an object of type Clock, intended primarily for deferred MIDI output. If you pass null, the timestamps in the events will be counted from the moment you call StartReceiving ().

 private void UpdateDevice() { if (d != null) { if (d.IsReceiving) d.StopReceiving(); if (d.IsOpen) d.Close(); } d = InputDevice.InstalledDevices[DeviceList.SelectedIndex]; if (!d.IsOpen) d.Open(); if (d.IsReceiving) d.StopReceiving(); d.StartReceiving(null); if (d != null) { d.NoteOn += new InputDevice.NoteOnHandler(this.NoteOn); d.NoteOff += new InputDevice.NoteOffHandler(this.NoteOff); d.ControlChange += new InputDevice.ControlChangeHandler(this.ControlChange); } } 


Do not forget to close the device upon exit.
 private void SettingsWindow_FormClosing(object sender, FormClosingEventArgs e) { d.StopReceiving(); d.Close(); } 


Click processing

I chose the operation algorithm as such: when all the keys are released, those that were pressed for longer than 50 ms are transmitted to the converter in an entire ensemble and sent to the active window using SendKeys. This makes it possible to score individual notes and chords.

The implementation is simple to ugliness:
NoteOn // NoteOff
 private List<Pitch> notes; private Dictionary<Pitch, float> events; //.... public void NoteOn(NoteOnMessage msg) { lock (this) { events[msg.Pitch] = msg.Time; } } public void NoteOff(NoteOffMessage msg) { lock (this) { if (events.ContainsKey(msg.Pitch)) { if (msg.Time - events[msg.Pitch] > 0.05) { notes.Add(msg.Pitch); } events.Remove(msg.Pitch); if ((events.Count == 0) && (notes.Count > 0)) { SendKeys.SendWait(" " + cons.Convert(notes)); notes.Clear(); } } } } 


I also added the ability to set leagues (parentheses in the lilypond syntax) using the pedal (and in my case the buttons) sustain. Since my keyboard is sending ControlChange two pieces each, I added an extra check.

 bool sustain; // ....... public void ControlChange(ControlChangeMessage msg) { if (msg.Control == Midi.Control.SustainPedal) { if ((msg.Value > 64) && !sustain) { sustain = true; SendKeys.SendWait("{(}"); } if ((msg.Value < 64) && sustain) { sustain = false; SendKeys.SendWait("{)}"); } } return; } 


The library also allows you to handle ProgramChange and PitchBend events.

We gnaw nuts of the elementary theory of music


The second task was more difficult than the first. I added a counter to the window to indicate the number of characters and the switch in major minor and began to think.

First of all, I created an intermediate link between the Midi.Pitch value and the output - the class of the gamma stage (lazy to look in the dictionary, I first named it Grade, but I had to Degree). Depending on the key, we will convert Midi.Pitch to Degree.
Arrays store the number conversion settings in the chromatic (12-step) scale to the 7-step scale and its alteration (increase or decrease).
PitchToGrade ()
 private static int[] majorScale = { 0, 1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6 }; private static int[] majorAcc = { 0, -1, 0, -1, 0, 0, 1, 0, -1, 0, -1, 0 }; private static int[] minorScale = { 0, 1, 1, 2, 2, 3, 3, 4, 5, 5, 6, 6 }; private static int[] minorAcc = { 0, -1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1 }; private Degree PitchToGrade(Pitch p) { //      int keybase = (keys * 7) % 12 - (isMajor?0:3); //          int offset = ((int)p - keybase) % 12; //     7-  int octave = (((int)p - keybase) / 12) * 7; int num, acc; if (offset < 0) offset += 12; if (isMajor) { num = majorScale[offset] + octave; acc = majorAcc[offset]; } else { num = minorScale[offset] + octave; acc = minorAcc[offset]; } return new Degree(num, acc); } 


After that, the most unpleasant begins: to find out which note and with what sign it will denote the stage with this number and this alteration in the given key.

The Degree class method works like this. The fs variable contains the minimum required number of characters in the key , which is necessary so that this step in this key in the non-altered form has an alternation sign. The Number variable stores the step number with respect to the octave, numMod without the count.

In a magical way (adding the number of key signs multiplied by four and taking two in the case of a minor), the step number turns into a note number in the white-tone scale, and, if the variable fs says it is necessary, add a “native” alteration of the tonality.
resolveIn ()
 private static String[] Naturals = { "c", "d", "e", "f", "g", "a", "h" }; private static String[] Sharps = { "cis", "dis", "eis", "fis", "gis", "ais", "his" }; private static String[] Flats = { "ces", "des", "es", "fes", "ges", "as", "b" }; private static String[] DoubleSharps = { "cisis", "disis", "eisis", "fisis", "gisis", "aisis", "bisis" }; private static String[] DoubleFlats = { "ceses", "deses", "eses", "feses", "geses", "ases", "beses" }; public String resolveIn(int keys, bool isMajor) { int fAcc = Acc; int fs; int fNum; int numMod = Number % 7; fs = isMajor ? 6 : 3; fs = (fs - 2*numMod) % 7; if (fs <= 0) fs += 7; if (keys < 0) fs = 8 - fs; if (fs <= Math.Abs(keys)) fAcc += keys / Math.Abs(keys); fNum = (numMod + keys*4 - (isMajor ? 0 : 2)) % 7; if (fNum < 0) fNum += 7; switch (fAcc) { case -2: return DoubleFlats[fNum]; case -1: return Flats[fNum]; case 0: return Naturals[fNum]; case 1: return Sharps[fNum]; case 2: return DoubleSharps[fNum]; default: return ""; } } 


The rest is simple: the need to add pitch-change characters is calculated by the step subtraction operator, and the last height is stored in the lastPitch field:
Convert () and everything else
 // class Degree public static String operator -(Degree a, Degree g) { int o; o = a.Number - g.Number; o = (int)Math.Round((double)o / 7.0); if (o > 0) return new String('\'', o); if (o < 0) return new String(',', -o); return ""; } // class PitchConsumer public String Convert(List<Pitch> pitches) { Pitch localLast = lastPitch; String accum; if ((int)lastPitch == 0) lastPitch = pitches[0]; pitches.Sort(); if (pitches.Count == 1) { accum = PitchToString(pitches[0], lastPitch); lastPitch = pitches[0]; } else { lastPitch = pitches[0]; accum = "<"; foreach (Pitch p in pitches) { if (accum.Length > 1) accum += " "; accum += PitchToString(p, localLast); localLast = p; } accum += ">"; } return accum; } private String PitchToString(Pitch p, Pitch last) { Degree g, glast; String note; g = PitchToGrade(p); glast = PitchToGrade(last); note = g.resolveIn(keys, isMajor); return note + (g - glast); } 



findings


The difficulty lay in wait for me not in the area where I was not sure, but in what is the subject of my specialty. But now I understand better how difficult it is for children in a music school :-). And it became really faster to type notes.

https://github.com/m0003r/LilyInput

Source: https://habr.com/ru/post/149075/


All Articles