2 " @Author: Eyolf Østrem (mailto:eyolf curlie oestrem small com)
3 " @Website: http://oestrem.com
4 " @License: GPL (see http://www.gnu.org/licenses/gpl.txt)
5 " @Created: 14-May-2008.
6 " @Last Change: Mon Nov 17 20:32:32 CET 2008
9 " macros for \ficta, \fermata, " \times, etc
10 " maps/plugin architecture
11 " customizable, user-defined layout and macros
13 " fix the spacing bugs that are still left (why won't pitch() enter a
14 " space before the note when the cursor is on the end of the line?)
16 "======================================================================
18 "======================================================================
19 " The script simplifies note entry for lilypond files. Three different
20 " kinds of tasks are performed with single or just-a-few key presses:
21 " - entry of a new note;
22 " - modification of an existing note (wrt duration, accidentals, octave,
23 " dots, cautionary accidentals, and articulation signs);
24 " - certain special signs, such as fermata, musica ficta, \times x/y {}, etc.
26 " The keyboard is completely remapped: the left hand enters the pitches, in
27 " the sequence of a piano keyboard, and the right hand 'plays' the rhythms,
28 " which are laid out 'ergonomically' from the \breve (B) to the 32nd note (P):
29 " 64th and 128th notes re-use the O and P keys in shifted position, and
30 " \longa and \maxima are placed on <S-l> and <S-m>.
31 " Flats and sharps are added with 'c' and 'v', octaves are modified with
32 " 'i' (up) and 'm' (down), and cautionary accidentals are entered with '!'
33 " and '?'. A \fermata is added with '.'
35 " -------------------------------------------------------------------------
36 " | s | g | a | b |times| | | ' |16/64|32/128 | |
37 " | Q | W | E | R | T | Y | U | I | O | P | | |
38 " ---------------------------------------------------------------------------
39 " | c | d | e | f | r/R | 1 | 2 | 4 | 8 | | | |
40 " | A | S | D | F | G | H | J | K | L | | | |
41 " -------------------------------------------------------------------------
42 " |undo | del |flat |sharp|breve| dot | , | | | | |
43 " | Z | X | C | V | B | N | M | | | | |
44 " -------------------------------------------------------------------
46 " The home row is used for the most common elements.
47 " The layout ensures that values that are likely to be close together
48 " (stepwise motion and leaps of fourths; 'f' + 'sharp', 'e' + 'flat';
49 " adjacent rhythm values, etc.) are close together also on the keyboard.
51 " Any of the "pitch keys" (asdfwer, plus qgG for s, r, and R) enters a
52 " single note name. Accidental modifications are rememebered, so one
53 " doesn't have to change every 'f' to 'fis' in g major. Modifications of
54 " the simple note is done subsequently. E.g., to turn
56 " f into fisis!,\breve..
58 " one would type the keys 'vv!mbnn' in any order.
60 "The mode is initialized on startup, or with the vim command Lyqi_init(). To
61 "enter music, run the function Lyqi_key(), which is an infinite loop (exit
64 "The arrow keys navigate between the note strings, and 'z' is mapped to
67 "======================================================================
69 "======================================================================
70 if exists("g:loaded_Lyqi")
73 delfun Get_current_note
76 let g:loaded_Lyqi = 1 " your version number
81 " initializes the values that are used throughout. All are immutable
82 " and are supposed to be so, except pitchmap, which will change
83 " according to the use of accidentals in the course of a piece.
89 global pitches, pitch_keys, pitchmap
90 pitches = ( "c", "d", "e", "f", "g", "a", "b", "s", "r", "R" )
91 pitch_keys = ( "a", "s", "d", "f", "w", "e", "r", "q", "g", "G" )
92 pitchmap = dict(zip(pitch_keys, pitches))
94 acc_keys = ( "c", "v" )
95 accmap = dict(zip(acc_keys, accs))
97 caut_keys = ( "!", "?" )
98 cautmap = dict(zip(caut_keys, cauts))
100 oct_keys = ( "m", "i" )
101 octmap = dict(zip(oct_keys, octs))
102 durs = ( "128", "64", "32", "16", "8", "4", "2", "1", "\\breve", "\\longa", "\\maxima" )
103 dur_keys = ( "P", "O", "p", "o", "l", "k", "j", "h", "b", "L", "M" )
104 durmap = dict(zip(dur_keys, durs))
107 valid_note = ("pitch", "acc", "caut", "oct", "dur", "dot", "art", "add")
108 current = { "pitch": "c", "acc": "", "caut": "", "oct": "", "dur": "", "dot": "" , "art": "", "add": "" }
111 notestring = r"""^(?P<pitch>[a-grsR])
112 (?P<acc>(((ses)|(s))|((es){1,2})|((is){1,2}))?)
115 (?P<dur>((16)|1|2|4|8|(32)|(64)|(\\breve)|(\\longa)|(\\maxima))?)
117 (?P<art>([-_^\\].*)*)
119 parsed_note = re.compile(notestring, re.VERBOSE)
121 # here begins the python code which does all the string processing and --
122 # eventually -- the midi output.
124 # - a wrapper function, process_key(), which decides which function to call
125 # depending on the input key,
126 # - specialized functions for each of the note-string elemenst (pitch, acc,
127 # caut, oct, dur, dot, art (for articulation signs etc.) and add
128 # (whatever is left over...)
129 # - make_note(), which generates the new note string.
131 # TODO: add functions for art and add
133 # Python functions {{{1
134 #======================================================================
136 #======================================================================
137 def parse(input_string):
138 match_obj = parsed_note.search(input_string)
140 current[i] = match_obj.group(i)
141 #adjust the inconsistent accidental syntax
142 if current['acc'].startswith('s'):
143 current['acc'] = 'e' + current['acc']
147 #======================================================================
148 # - change current['pitch']
149 # - TODO: play a sound according to cur_note['pitch'] and [oct]
151 def pitch(input_key):
152 current['pitch'] = pitchmap[input_key]
154 if vim.eval("col('.')") == 1:
155 vim.command("normal i" + n)
156 elif vim.eval("col('$')-col('.')") == 1:
157 vim.command("normal a " + n)
160 vim.command("normal a" + n)
161 #vim.command("normal a ")
163 #======================================================================
166 #======================================================================
168 vim.command("call Get_current_note()")
170 note = vim.eval("b:notestring")
172 #calculate the new value for acc -- up or down?
173 if 'e' in current['acc']:
177 accnum = len(current['acc']) / 2 * esis + accmap[input_key]
179 current['acc'] = 'eses'
181 current['acc'] = 'es'
185 current['acc'] = 'is'
187 current['acc'] = 'isis'
189 if pitchmap[k][:1] == current['pitch']:
190 pitchmap[k] = current['pitch'] + current['acc']
191 vim.command("normal i" + make_note())
193 #======================================================================
195 #======================================================================
197 vim.command("call Get_current_note()")
198 note = vim.eval("b:notestring")
200 current['dur'] = durmap[input_key]
202 vim.command("normal i" + make_note())
204 #======================================================================
205 #cautionary accidentals {{{2
206 #======================================================================
208 vim.command("call Get_current_note()")
209 note = vim.eval("b:notestring")
211 current['caut'] = cautmap[input_key]
212 vim.command("normal i" + make_note())
214 #======================================================================
216 #======================================================================
218 #get current note from vim and parse it into current{}
219 vim.command("call Get_current_note()")
220 note = vim.eval("b:notestring")
222 if ',' in current['oct']:
225 elif "'" in current['oct']:
230 if octmap[input_key] == -1:
234 octnum = abs(len(current['oct']) * octdir + octmap[input_key])
235 current['oct'] = octnum * octsign
236 vim.command("normal i" + make_note())
238 #======================================================================
240 #======================================================================
241 # TODO: make function for backwards scanning after rhythm value. In the
242 # meantime, a default value of 4 will have to do.
244 vim.command("call Get_current_note()")
245 note = vim.eval("b:notestring")
247 if not current['dur']:
249 current['dot'] += '.'
250 vim.command("normal i" + make_note())
254 #def find_prev_dur():
257 # dur_search[i] = '\\(' + durs[i] + '\\)'
258 # dur_str = '\\|'.join(dur_search)
259 # dur_match = vim.command("call search("+search_str+", 'bcpn')")
260 # current['dur'] = durs[dur_match-1]
262 #======================================================================
264 #======================================================================
268 new_note += current[i]
271 #======================================================================
273 #======================================================================
275 key = vim.eval("b:input_key")
276 if key in pitch_keys:
278 elif key in acc_keys:
280 elif key in oct_keys:
282 elif key in caut_keys:
284 elif key in dur_keys:
286 elif key in dot_keys:
289 vim.command("normal a " + key)
296 "======================================================================
297 "function! Get_current_note() {{{2
298 "======================================================================
299 " collects the note-string prior to the cursor position (here only a
300 " rudimentary check is done: the first string which begins with one of
301 " the note-characters)
302 " So far limited to plain strings; chords will have to come at a later
304 function! Get_current_note()
305 call search('\<[a-gRrs]', 'bc')
306 "let save_cursor = getpos(".")
308 let b:notestring = getreg('"')
309 "call setpos('.', save_cursor)
311 "======================================================================
313 "======================================================================
315 2match Error /\<\S\{-}\%#\S\{-}\>\|^\%#\s*/
316 match WarningMsg /\%#/
318 while b:input_key != "å"
319 "positions the cursor at current or following whitespace. Doesn't
320 "capture repeated whitespace, but never mind... can be cleaned up with
322 "call cursor(".", searchpos('\_s', 'ce')[1])
323 call search('\_s', 'c')
326 " navigation keys; interpreted directly
327 let b:input_key = getchar()
328 if b:input_key == "\<Left>"
329 call search('\_s', 'b')
331 match Error /\<\S\{-}\%#\S\{-}\>\|^\%#\s*/
333 elseif b:input_key == "\<Right>"
334 call search('\_s', '')
336 match Error /\<\S\{-}\%#\S\{-}\>\|^\%#\s*/
338 elseif b:input_key == "\<Down>"
340 call search('\_s', '')
342 match Error /\<\S\{-}\%#\S\{-}\>\|^\%#\s*/
344 elseif b:input_key == "\<Up>"
346 call search('\_s', '')
348 match Error /\<\S\{-}\%#\S\{-}\>\|^\%#\s*/
351 " character keys; interpreted after conversion to char
352 let b:input_key = nr2char(b:input_key)
353 if b:input_key == 't'
354 exe "normal a\\times " . input("Fraction: ", "2/3") . " { "
357 if b:input_key == '}'
362 elseif b:input_key == '.'
363 exe "normal a\fermata "
366 elseif b:input_key == '\'
367 exe "normal a\\" . input("Escaped sequence: ") . " "
370 elseif b:input_key == 'x'
371 call Get_current_note()
372 let line = getline('.')
373 substitute(eval(line), " \+", " ", "g")
376 elseif b:input_key == 'z'
380 elseif b:input_key == 'Z'
397 command! LyqiMode :call Lyqi_key()
398 noremap <f2> LyqiMode<cr>