Fixed bug with spacing (I think)
[lyqi-vim.git] / lyqi.py
blobd8f6f474831d128cc5d028d19d78a31747beab22
1 # coding: utf8
3 #======================================================================
4 #initialize values{{{1
5 #======================================================================
6 # pitches (rhythms, ...) contains the pitch names used in the file, plus
7 # "s" and "r" pitch_keys is a (user-defined) variable list of keyboard keys
8 # to go with the various pitch names pitchmap gives the current translation
9 # between keyb. input and output to document:
10 # pitchmap["a"] = "c". The dictionary is initialized with default values,
11 # but will be changed along the way, to store the current values at any
12 # time, so that after a "is" is added to "c", pitchmap["a"] = "cis"
14 import re
15 import math
16 import vim
18 loaded = vim.eval("g:loaded_lyqi")
19 if loaded == 1:
20 initialize()
22 def initialize():
23 global pitches, pitch_keys, pitchmap
24 pitches = ( "c", "d", "e", "f", "g", "a", "b", "s", "r", "R" )
25 pitch_keys = ( "a", "s", "d", "f", "w", "e", "r", "q", "g", "G" )
26 pitchmap = dict(zip(pitch_keys, pitches))
27 vim.command("let g:loaded_lyqi = 0")
28 return pitchmap
31 accs = ( -1, 1 )
32 acc_keys = ( "c", "v" )
33 accmap = dict(zip(acc_keys, accs))
34 cauts = ( "!", "?" )
35 caut_keys = ( "!", "?" )
36 cautmap = dict(zip(caut_keys, cauts))
37 octs = ( -1, 1 )
38 oct_keys = ( "u", "i" )
39 octmap = dict(zip(oct_keys, octs))
40 durs = ( "128", "64", "32", "16", "8", "4", "2", "1", "\\breve", "\\longa", "\\maxima" )
41 dur_keys = ( "P", "O", "p", "o", "l", "k", "j", "h", "b", "L", "M" )
42 durmap = dict(zip(dur_keys, durs))
43 dots = ( "." )
44 dot_keys = ( "n" )
46 valid_note = ("pitch", "acc", "caut", "oct", "dur", "dot", "art", "add")
48 current = {
49 "pitch": "c",
50 "acc": "",
51 "caut": "",
52 "oct": "",
53 "dur": "",
54 "dot": "" ,
55 "art": "",
56 "add": ""
58 new_note = ""
59 vim_note = ""
61 # RE for parsing an input string representing a note name. The RE-string
62 # matches everything from "a" to "ases!,,\maxima...^\f", and also takes
63 # care of the syntactic inconsistency which allows both "es/as" and
64 # "ees/aes".
66 notestring = r"""^(?P<pitch>[a-g])
67 (?P<acc>(((ses)|(s))|((es){1,2})|((is){1,2}))?)
68 (?P<caut>[?!]*)
69 (?P<oct>[,']*)
70 (?P<dur>(1|2|4|8|(16)|(32)|(64)|(\\breve)|(\\longa)|(\\maxima))?)
71 (?P<dot>[.]*)
72 (?P<art>([-_^\\].*)*)
73 (?P<add>.*)$"""
76 #======================================================================
77 #Functions {{{1
78 #======================================================================
79 #vim interaction {{{2
80 #======================================================================
81 def get_vim_key():
82 input_key = vim.eval("b:input_key")
83 return input_key
84 #======================================================================
85 def process_key(key):
87 if key in pitch_keys:
88 pitch(key)
89 elif key in acc_keys:
90 acc(key)
91 elif key in oct_keys:
92 oct(key)
93 elif key in caut_keys:
94 caut(key)
95 elif key in dur_keys:
96 dur(key)
97 elif key in dot_keys:
98 dot()
99 else:
100 vim.command("normal a" + key)
102 #Parse {{{2
103 #======================================================================
104 def parse(input_string):
105 parsed_note = re.compile(notestring, re.VERBOSE)
106 match_obj = parsed_note.search(input_string)
107 for i in valid_note:
108 current[i] = match_obj.group(i)
109 #adjust the inconsistent accidental syntax
110 if current['acc'].startswith('s'):
111 current['acc'] = 'e' + current['acc']
114 #pitch {{{2
115 #======================================================================
116 # - forandre current['pitch']
117 # - avspille en lyd i overensstemmelse med cur_note['pitch'] og [oct]
119 def pitch(input_key):
120 current['pitch'] = pitchmap[input_key]
121 n = current['pitch']
122 vim.command("normal a" + n + " ")
124 #======================================================================
125 #acc {{{2
126 #======================================================================
127 def acc(input_key):
128 #get current note from vim and parse it into current{}
129 vim.command("call Get_current_note()")
130 global note
131 note = vim.eval("b:notestring")
132 parse(note)
133 #calculate the new value for acc -- up or down?
134 if 'e' in current['acc']:
135 esis = -1
136 else:
137 esis = 1
138 accnum = len(current['acc']) / 2 * esis + accmap[input_key]
139 if accnum < -1:
140 current['acc'] = 'eses'
141 elif accnum == -1:
142 current['acc'] = 'es'
143 elif accnum == 0:
144 current['acc'] = ''
145 elif accnum == 1:
146 current['acc'] = 'is'
147 else:
148 current['acc'] = 'isis'
149 # her er det en feil: jeg forandrer key og ikke val, eller tvert om ; er
150 # for trøtt til å fikse det nå.
151 for k in pitchmap:
152 if pitchmap[k] == current['pitch']:
153 pitchmap[k] = current['pitch'] + current['acc']
154 vim.command("normal a" + make_note())
156 def reverse_lookup(d,v):
157 for k in d:
158 if d[k] == v:
159 return k
161 #======================================================================
162 #dur {{{2
163 #======================================================================
164 def dur(input_key):
165 #get current note from vim and parse it into current{}
166 vim.command("call Get_current_note()")
167 note = vim.eval("b:notestring")
168 parse(note)
169 current['dur'] = durmap[input_key]
170 current['dot'] = ''
171 vim.command("normal a" + make_note())
173 #======================================================================
174 # make_note {{{2
175 #======================================================================
176 def make_note():
177 # bruker bare current, så ingen args er nødvendige
178 new_note = ""
179 for i in valid_note:
180 new_note += current[i]
181 return new_note
183 #======================================================================
184 # cautionary accidentals {{{2
185 #======================================================================
186 def caut(input_key):
187 vim.command("call Get_current_note()")
188 note = vim.eval("b:notestring")
189 parse(note)
190 current['caut'] = cautmap[input_key]
191 vim.command("normal a" + make_note())
193 #======================================================================
194 # octave signs {{{2
195 #======================================================================
196 def oct(input_key):
197 #get current note from vim and parse it into current{}
198 vim.command("call Get_current_note()")
199 note = vim.eval("b:notestring")
200 parse(note)
201 if ',' in current['oct']:
202 octdir = -1
203 octsign = ','
204 elif "'" in current['oct']:
205 octdir = 1
206 octsign = "'"
207 else:
208 octdir = 0
209 if octmap[input_key] == -1:
210 octsign = ','
211 else:
212 octsign = "'"
213 octnum = abs(len(current['oct']) * octdir + octmap[input_key])
214 current['oct'] = octnum * octsign
215 vim.command("normal a" + make_note())
217 #======================================================================
218 #dot {{{2
219 #======================================================================
220 #if input = dot og dur ikke er definert:
221 # #scan tilbake etter siste dur
222 # TODO: make function for backwards scanning after rhythm value.
223 # In the meantime, a default value of 4 will have to do.
224 # dur = siste_dur
225 def dot():
226 vim.command("call Get_current_note()")
227 note = vim.eval("b:notestring")
228 parse(note)
229 if not current['dur']:
230 current['dur'] = '4'
231 current['dot'] += '.'
232 vim.command("normal a" + make_note())
235 #def find_prev_dur():
236 # dur_search = []
237 # for i in durs:
238 # dur_search[i] = '\\(' + durs[i] + '\\)'
239 # dur_str = '\\|'.join(dur_search)
240 # dur_match = vim.command("call search("+search_str+", 'bcpn')")
241 # current['dur'] = durs[dur_match-1]
244 # %======================================================================
245 # Faste tegn
246 # %======================================================================
247 # slurs ~ settes inn direkte, med luft rundt
248 # rest/silent (oppfører seg som vanlige noter, bortsett fra at deres verdi
249 # ikke forandres).
250 # \ - settes inn direkte, og avviker midlertidig fra lyqi-mode, inntil ...
251 # hva? det trykkes \ igjen? Kanskje. Eller som i emacs: at det
253 # funksjon for å forandre notetrinn
254 # TODO- oppdaterer pitch
255 # - fjerner [acc]-verdi (så "fes" og "fis" blir til "e"
256 # - men endrer selvfølgelig ikke pitches
258 # add_markup [introduced by "\"; leaves lyqi-mode; return with <esc>]
260 # output
262 #%======================================================================
263 #change_degree {{{2
264 #======================================================================
265 # change_degree: trenger kanskje kommando for å forandre degree (ikke
266 # samme som aug)
269 # -
274 process_key(get_vim_key())
276 #======================================================================
277 #Chords {{{1
278 #%======================================================================
279 # NB: unntak: chords, der [dur] står utenfor:
281 # < [pitch][oct][art] > [dur][dot]
283 # Chords er dessuten et spesialtilfelle som må tas hensyn til mht.
284 # forandring av rytmeverdi: prøver man å sette inn en 4 eller en . etter
285 # en uavsluttet <, skal det gis feilmelding, og trykker man 4 mens man er
286 # innenfor en <>-blokk, skal det søkes framover, og ikke bakover.
290 # TODO: hopper over midi-kommandoene enn så lenge
293 #%======================================================================
294 #Fra lyqi-tool (emacs) {{{2
295 #%======================================================================
297 #abspitch
298 #%======================================================================
301 #( let (( abspitch1 ( + ( * 7 ( lyqi-note-octave prevnote)) (lyqi-note-pitch prevnote)))
302 #abspitch1 =
303 #finn forrige notes oktav, gange med 7 og legg til tallet for forrige note
304 #prevnote er en array som inneholderzc flere verdier: alle
305 #(abspitch2 (+ (* 7 (lyqi-note-octave note)) (lyqi-note-pitch note)))
307 #(if (< (abs (- abspitch1 abspitch2)) 4)
308 ##" ; same relative octave
309 #(if (> abspitch1 abspitch2)
310 #(make-string (+ (/ (- abspitch1 abspitch2 4) 7) 1)
311 #(lyqi-get-translation 'octave-down))
312 #(make-string (+ (/ (- abspitch2 abspitch1 4) 7) 1)
313 #(lyqi-get-translation 'octave-up)))))
315 #(type 'note "note, rest or skip")
316 #(pitch 0 "the actual note, from 0 (do) to 6 (si)")
317 #(accidental 2 "from 0 (##) to 4 (bb)")
318 #(octave 1 "octave 0 is starting with the do which
319 #is in the second interline in a fourth line F-clef")
320 #(duration 3 "from 1 to 8, real-duration = 2^(duration - 1)")
321 #(dots 0 "number of dots, from 0 to 4")
322 #(force-duration nil "tells if duration must be written")
323 #(previous nil "The previous note"))
326 #%======================================================================
327 #Vim-options
328 #%======================================================================
329 #let g:lyqi_midi_command = "timidity -iA -B2,8 -0s -EFreverb=0"
330 #let g:lyqi_midi_kbd = "mymidikbd"
331 #let g:lyqi_use_midi = 1 #skal midi-processen starte automatisk?
332 # evt. intro for vim
333 #if &cp || exists("loaded_lyqi")
334 #finish
335 #endif
336 #let loaded_lyqi = 1
340 #======================================================================
341 #Flowchart {{{1
342 #======================================================================
344 #input fra vim:
345 #se lyqi.vim
347 #Tastetrykk.
348 # (1) oversett til notenavn
349 # (2) hvilken gruppe tilhører det? Gå til tilsvarende funksjon
351 # notenavn:
352 # (3) finne posisjon (etter streng under eller før cursor)
353 # (4) oppdatere current med det nye notenavnet
354 # (5) lage ny notestreng (som bare består av notenavn)
355 # (6) innføre strengen i dok. på den funne posisjon
356 # (7) spille lyd
358 # rytme: (ex: "j")
359 # (8) hente streng under eller før cursor
360 # (9) parse streng
361 # (4) oppdatere med ny verdi
362 # (5) lage ny streng
363 # (10) erstatte gammel med ny streng
364 # (7) spille lyd
366 # oktav, caut, dot:
367 # som ovenfor
369 # artikulasjon:
370 ## to typer, som trigges av "\" og
371 # (11)
373 # Programmet skal:
376 # 1. remappe tangentbordet, gjeldende for bufferen
378 # 2. inneholde funksjoner for å modifisere foregående tekst, i følgende
379 # tilfeller ("tekst" i dette tilfelle betyr hele blokken, dvs. strengen før
380 # eller under cursor, omgitt enten av whitespace eller <>, og parset i
381 # forhold til en standardgruppe):
383 # ny degree: tilføyer et nytt noteobjekt, og oppdaterer $current. Eneste
384 # grunn til å gjøre det, er for midi-ens del. En degree-inntastning skal
385 # aldri i seg selv føre til en mer omfattende streng, men det må hele
386 # tiden holdes styr på degree og oktav for sist innskrevne note.
388 # når en ny rytmeverdi skrives inn: da skal verdien føyes til foregående
389 # note, eller eksisterende verdi forandres, og $current oppdateres.
391 # punkteringer: ren strengemodifikator: punkt legges til eksplisitt
392 # rytmeverdi i strengen, hvis den eksisterer, eller forbindes med
393 # gjeldende rytmeverdi og føyes til etter pitch-gruppe. Trenger forsåvidt
394 # ikke lagres (vil man noen gang vite hvor mange punkteringer foregående=
395 # note hadde? I think not), men det skader vel ikke...
397 # når et oktavtegn skrives inn: to ting skjer: strengen forandres på
398 # enkelt vis (', legges til degree), og $current oppdateres
400 # når en akksidental skrives inn: da skal det føyes is/es til foregående
401 # degree, og dens verdi skal lagres, dvs. keyboard-kommandoen skal
402 # forandres temporært.
404 # Input-typer: degree_key, octave_key, rhythm_key, dot, change_aug, change_degree
406 # Lagrede interne variabler:
407 # curr_note
408 # prev_note
409 # default note constructions
410 # curr_rhythm | disse to er kanskje unødvendige; de er allerede del
411 # curr_pitch | av curr_note
413 #Modifikasjonen skjer "utenfor", dvs i en ekstern funksjon som sammenligner
414 #den parsede streng (curr_note) med en standard (note) og modifiserer i
415 #overensstemmelse med input.
417 # nødvendige funksjoner:
418 # - parse input: strengen kan variere fra "a" til "ais'4...^.[{
419 # den skal så deles opp og fylle en liste (degree, aug, oct, rhythm,
420 # dot, articulation) for current_note
421 # - oppgradere prev_values med den nye verdien
422 # - forandre strengen etter sml mellom curr_note og prev_values
423 # - føre strengen tilbake til tekstfilen (og avspille en note)
425 # - pitch_acc
426 # - rhythm_dot
427 # vim: fdm=marker