├── CONTRIBUTORS ├── LICENSE ├── README.md ├── ftdetect └── txbt.vim ├── ftplugin └── txbt.vim ├── pythonx └── txbtclient.py └── syntax └── txbt.vim /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | Grady O'Connell 2 | David Briscoe 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Grady O'Connell 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vim-textbeat 2 | 3 | Write music in vim using [textbeat](https://github.com/flipcoder/textbeat) 4 | 5 | This is only the plug-in repo. The backend code is in the repo above. 6 | 7 | ![Screenshot](https://i.imgur.com/HmzNhXf.png) 8 | 9 | WORK IN PROGRESS 10 | 11 | ## Install 12 | 13 | Install as a vim plugin. 14 | 15 | Clone [textbeat](https://github.com/flipcoder/textbeat). 16 | 17 | Run textbeat's `setup.py` to install dependencies and make `textbeat` 18 | python module available. Must use python3! 19 | 20 | And add the txbt path to your vimrc: 21 | 22 | if has('win32') 23 | let g:textbeat_path = 'path-to-textbeat/txbt.cmd' 24 | else 25 | let g:textbeat_path = 'path-to-textbeat/txbt' 26 | endif 27 | -------------------------------------------------------------------------------- /ftdetect/txbt.vim: -------------------------------------------------------------------------------- 1 | autocmd BufNewFile,BufRead *.txbt setfiletype txbt 2 | -------------------------------------------------------------------------------- /ftplugin/txbt.vim: -------------------------------------------------------------------------------- 1 | set ve=all 2 | 3 | if v:version < 800 4 | finish 5 | endif 6 | if !has('pythonx') 7 | finish 8 | endif 9 | 10 | pyx import txbtclient 11 | 12 | function! txbt#play() 13 | pyx txbtclient.VimTextbeat.play() 14 | endfunc 15 | function! txbt#stop() 16 | pyx txbtclient.VimTextbeat.play() 17 | endfunc 18 | function! txbt#playline() 19 | pyx txbtclient.VimTextbeat.playline() 20 | endfunc 21 | function! txbt#reload() 22 | pyx txbtclient.VimTextbeat.reload() 23 | endfunc 24 | function! txbt#poll(a) 25 | pyx txbtclient.VimTextbeat.poll() 26 | endfunc 27 | function! txbt#starttimer() 28 | if exists('s:txbttimer') 29 | call timer_stop(s:txbttimer) 30 | endif 31 | let s:txbttimer = timer_start(20, 'txbt#poll', {'repeat':-1}) 32 | endfunc 33 | function! txbt#stoptimer() 34 | call timer_stop(s:txbttimer) 35 | unlet s:txbttimer 36 | endfunc 37 | 38 | command! -buffer -nargs=0 TextbeatPlay call txbt#play() 39 | command! -buffer -nargs=0 TextbeatPlayLine call txbt#playline() 40 | command! -buffer -nargs=0 TextbeatReload call txbt#reload() 41 | command! -buffer -nargs=0 TextbeatStartTimer call txbt#starttimer() 42 | command! -buffer -nargs=0 TextbeatStopTimer call txbt#stoptimer() 43 | 44 | augroup textbeat 45 | au! BufRead,BufWritePost 46 | au BufRead,BufWritePost TextbeatReload 47 | augroup end 48 | 49 | if !exists("g:textbeat_no_mappings") || !g:textbeat_no_mappings 50 | "nmap :TextbeatPlay 51 | "nmap :TextbeatStop 52 | "nmap :TextbeatPlayLine 53 | endif 54 | 55 | if exists("g:textbeat_path") 56 | pyx txbtclient.VimTextbeat.set_txbt_path(vim.eval("g:textbeat_path")) 57 | else 58 | pyx txbtclient.VimTextbeat.set_txbt_path("textbeat") 59 | endif 60 | 61 | if exists("g:textbeat_python") 62 | pyx txbtclient.VimTextbeat.set_python(vim.eval("g:textbeat_python")) 63 | else 64 | pyx txbtclient.VimTextbeat.set_python("python") 65 | endif 66 | 67 | -------------------------------------------------------------------------------- /pythonx/txbtclient.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import vim,subprocess,os,select 3 | 4 | class VimTextbeatPlugin: 5 | DEVNULL = open(os.devnull, 'w') 6 | 7 | def set_python(self,p): 8 | self.PYTHON = os.path.expanduser(p) 9 | 10 | def set_txbt_path(self,p): 11 | self.TXBT_PATH = os.path.expanduser(p) 12 | 13 | def check_paths(self): 14 | if not self.TXBT_PATH: 15 | print("vim-textbeat: in your .vimrc, set the locations:") 16 | # print(" let g:python_path = '/usr/bin/python'") 17 | print(" let g:textbeat_path = '~/bin/textbeat/txbt'") 18 | print("In the future, this will be automatic.") 19 | return False 20 | return True 21 | 22 | def __init__(self): 23 | self.processes = [] 24 | self.process_buf = {} 25 | self.playing = False 26 | self.TXBT_PATH = None 27 | 28 | def stop(self): 29 | term = 0 30 | for p in self.processes: 31 | if p.poll()==None: 32 | try: 33 | p.terminate() 34 | except: 35 | pass 36 | term += 1 37 | self.process_buf = {} 38 | self.processes = [] 39 | return term 40 | def refresh(self): 41 | self.processes = filter(lambda p: p.poll()!=None, self.processes) 42 | def play(self): 43 | if not self.check_paths(): return 44 | if self.stop(): 45 | return 46 | vim.command('call txbt#starttimer()') 47 | vim.command('set cursorline') 48 | print('Playing') 49 | p = subprocess.Popen([\ 50 | self.TXBT_PATH,\ 51 | '--follow', 52 | '+'+str(max(0,vim.current.window.cursor[0]-1)),\ 53 | vim.current.buffer.name\ 54 | ], 55 | stdout=subprocess.PIPE, stderr=subprocess.PIPE, 56 | bufsize=1, universal_newlines=True 57 | ) 58 | self.processes.append(p) 59 | self.process_buf[p] = '' 60 | def playline(self): 61 | if not self.check_paths(): return 62 | self.stop() 63 | p = subprocess.Popen([\ 64 | self.TXBT_PATH,\ 65 | '-l',\ 66 | vim.current.line\ 67 | ], stdout=self.DEVNULL, stderr=self.DEVNULL) 68 | self.processes.append(p) 69 | self.process_buf[p] = '' 70 | def poll(self): 71 | running = 0 72 | done = False 73 | active = 0 74 | for p in self.processes: 75 | # phash = hash(p) 76 | working = False 77 | if p.poll()==None: 78 | active += 1 79 | while True: 80 | sel = None 81 | try: 82 | sel = select.select([p.stdout],[],[],0) 83 | if not sel or not sel[0]: 84 | break 85 | except: 86 | break 87 | buf = p.stdout.read(1) 88 | if not buf: 89 | break 90 | pbuf = self.process_buf[p] 91 | if buf=='\n': 92 | # vim.command('normal! '+str(buf.count('\n'))+'j') 93 | vim.command('exe ' + str(int(pbuf)+1)) 94 | self.process_buf[p] = '' 95 | else: 96 | self.process_buf[p] += buf 97 | working = True 98 | if working: 99 | running += 1 100 | if not active: 101 | print('Stopped') 102 | vim.command('call txbt#stoptimer()') 103 | vim.command('set cursorline&') 104 | return running 105 | def reload(self): 106 | for i in xrange(2): # first 2 lines check for header 107 | try: 108 | line = vim.current.buffer[i] 109 | except: 110 | return 111 | if line.startswith('%'): 112 | ctrls = line[1:].split(' ') 113 | for ctrl in ctrls: 114 | if ctrl.startswith('c') or ctrl.startswith('C'): 115 | ctrl = ctrl[1:] 116 | if ctrl[0]=='=': 117 | ctrl = ctrl[1:] 118 | cs = ctrl.split(',') 119 | if len(cs)==1: cs.append('0') 120 | x = str(int(cs[0])+int(cs[1])) 121 | if len(cs)>=2: 122 | vim.command('let &l:colorcolumn = join(range('+\ 123 | x +',100,'+cs[0]+'),\',\')') 124 | return 125 | 126 | 127 | VimTextbeat = VimTextbeatPlugin() 128 | 129 | -------------------------------------------------------------------------------- /syntax/txbt.vim: -------------------------------------------------------------------------------- 1 | if version < 600 2 | syntax clear 3 | elseif exists("b:current_syntax") 4 | finish 5 | endif 6 | 7 | syn match txbtComment '^;.*' 8 | syn match txbtBus '^%.*' 9 | "syn match txbtLabel '|\w*:' 10 | "syn match txbtCall ':\w*|' 11 | syn match txbtNoteNumber '[b#]*[0-9]+' 12 | syn match txbtNoteName '[A-G][b#]*' 13 | "syn match txbtPop '|||' 14 | "syn region txbtLabel start='|^|' end=':' 15 | "syn region txbtCall start=':' end='|' 16 | 17 | "syn keyword txbtKeywords m ma maj min dim dom mu 18 | 19 | hi def link txbtBus Macro 20 | hi def link txbtComment Comment 21 | hi def link txbtLabel Label 22 | hi def link txbtCall Label 23 | hi def link txbtNoteNumber Number 24 | hi def link txbtNoteName Number 25 | hi def link txbtPop Label 26 | "hi def link txbtNoteNumber Number 27 | "hi def link txbtNoteName Number 28 | 29 | let b:current_syntax = "txbt" 30 | --------------------------------------------------------------------------------