Audio/AudioControl: Add Metronome
g0dil [Fri, 8 Feb 2008 10:14:05 +0000 (10:14 +0000)]
Audio/Audiocontrol: Add alsaplayer 'skip' command
Audio/AudioControl: Add binding to queue 'TM' recordings into alsaplayer
Audio/AudioControl: Make Joyboard more robust (don't crash if input device does not exist)

AlsaPlayer.py
Joyboard.py
Metronome.py [new file with mode: 0644]
config.py
start.sh

index 1c6f47a..6a40b21 100644 (file)
@@ -26,6 +26,9 @@ class Player(object):
         else:
             self.speed(0.0)
 
+    def skip(self, distance):
+        self._command('--relative', str(distance))
+
     def stop(self):
         self._command('--stop')
 
@@ -69,6 +72,7 @@ class Dispatcher(object):
 
 Play   = Dispatcher('play')
 Pause  = Dispatcher('pause')
+Skip   = Dispatcher('skip')
 Stop   = Dispatcher('stop')
 Toggle = Dispatcher('toggle')
 Prev   = Dispatcher('prev')
index be8af89..ef06b87 100644 (file)
@@ -118,11 +118,13 @@ class Controller(Views.View):
         self._parameter = None
         self._keylist = keylist
         self._keymap = Bindings.KeyMap()
-        self._keymap.add( Bindings.Binding(Events.Event(source.context(), 'p%d' % controller), '',
-                                           Actions.Command('controllerChanged',
-                                                           self._controllerChanged)) )
+        if source:
+            self._keymap.add( Bindings.Binding(Events.Event(source.context(), 'p%d' % controller), '',
+                                               Actions.Command('controllerChanged',
+                                                               self._controllerChanged)) )
         self._keylist.prepend(self._keymap)
-        source.registerController(controller, low, high)
+        if source:
+            source.registerController(controller, low, high)
         self._dispatcher = dispatcher
         self._min = None
         self._max = None
@@ -148,12 +150,16 @@ class Controller(Views.View):
         self._min = min
         self._max = max
         self._stops = stops
-        self._keymap.add( Bindings.Binding( self._valueEvent, '',
-                                            Actions.Command('updateValue',
-                                                            self._updateValue)) )
+        if self._valueEvent:
+            self._keymap.add( Bindings.Binding( self._valueEvent, '',
+                                                Actions.Command('updateValue',
+                                                                self._updateValue)) )
         self._keylist.prepend(self._keymap)
         self._value = None
-        self._valueCommand()
+        if self._valueEvent:
+            self._valueCommand()
+        else:
+            self._value = self._valueCommand()
         self._redraw()
 
     def _updateValue(self, binding):
@@ -166,16 +172,18 @@ class Controller(Views.View):
         Logger.log('ctl',"value = %d" % event.value)
         self._controlValue = event.value
         if self._controlValue >= 999 or self._controlValue <= 1:
-            self._dispatcher.setIdleCallback(self._changeValue,50)
+            self._dispatcher.setIdleCallback(self._changeValue,75)
+        elif self._controlValue >= 700 or self._controlValue <= 200:
+            self._dispatcher.setIdleCallback(self._changeValue,200)
         else:
             self._dispatcher.unsetIdleCallback()
         self._redrawController()
 
     def _changeValue(self):
         if self._value is None: return
-        if self._controlValue >= 999:
+        if self._controlValue > 500:
             self.stepValue(+1)
-        elif self._controlValue <= 1:
+        else:
             self.stepValue(-1)
 
     def stepValue(self, direction):
@@ -198,7 +206,11 @@ class Controller(Views.View):
             self._redrawValue()
         else:
             self._setCommand(newValue)
-            self._valueCommand()
+            if self._valueEvent:
+                self._valueCommand()
+            else:
+                self._value = self._valueCommand()
+                self._redrawValue()
 
     def _redraw(self):
         height, width = self.win().getmaxyx()
@@ -241,11 +253,11 @@ class Controller(Views.View):
 
     def _redrawController(self, refresh=True):
         height, width = self.win().getmaxyx()
-        if self._controlValue is not None and self._controlValue >= 999:
+        if self._controlValue is not None and self._controlValue >= 700:
             self.win().addch(3,3,curses.ACS_UARROW)
         else:
             self.win().addch(3,3,curses.ACS_TTEE)
-        if self._controlValue is not None and self._controlValue <= 1:
+        if self._controlValue is not None and self._controlValue <= 200:
             self.win().addch(height-3,3,curses.ACS_DARROW)
         else:
             self.win().addch(height-3,3,curses.ACS_BTEE)
@@ -279,8 +291,10 @@ def register( viewmanager,
               bits = None,
               controllers = []):
     viewmanager.registerView( View(context, label, numeric_switches, alpha_switches, x, y, dx, size) )
-    source = Source(device, context, bits)
-    dispatcher.registerSource( source )
+    source = None
+    if device is not None:
+        source = Source(device, context, bits)
+        dispatcher.registerSource( source )
     return source
 
 def registerController( viewmanager, dispatcher, keylist, source, context, name, x, y, dx, dy,
diff --git a/Metronome.py b/Metronome.py
new file mode 100644 (file)
index 0000000..b527f4e
--- /dev/null
@@ -0,0 +1,195 @@
+import Actions, OSC, Events, Views, curses
+import eci, time
+
+class Metronome(Views.View):
+
+    def __init__(self, context, label, x, y, dx, dy, dispatcher):
+        Views.View.__init__(self, context, label, x, y, dx, dy)
+        self._dispatcher = dispatcher
+        self._eci = eci.ECI(1)
+        self._eci('-G:jack,metronome,notransport')
+        self._eci('ai-add null')
+        self._eci('ao-add jack_auto,alsa_pcm')
+        self._eci('cop-add -pn:metronome,120')
+        self._eci('cop-add -ea:1')
+        self._eci('engine-launch')
+        self._bpm = 120
+        self._volume = 1
+        self._running = False
+        self._tap = None
+
+    def updateView(self, bindings):
+        pass
+
+    def init(self):
+        Views.View.init(self)
+        self._redraw(0)
+
+    def _redraw(self, refresh=1):
+        self.win().addstr(1,2,'%3d bpm' % self._bpm)
+        if self._running:
+            self.win().addstr(1,10,'Running',curses.A_BOLD)
+        else:
+            self.win().addstr(1,10,'Off    ')
+        self.win().addstr(1,18,'%3.1f' % self._volume)
+        if refresh:
+            self.win().refresh()
+        
+    def toggle(self):
+        self._running = not self._running
+        if self._running:
+            self._eci('start')
+        else:
+            self._eci('stop')
+        self._redraw()
+
+    def tap(self):
+        t = time.time()
+        if self._tap and self._tap < t and self._tap > t-2:
+            self.setBPM(60.0 / (t - self._tap))
+        self._tap = t
+
+    def setVolume(self, value):
+        self._volume = value
+        self._eci('cop-select 2')
+        self._eci('copp-select 1')
+        self._eci('copp-set', self._volume)
+        self._redraw()
+
+    def setBPM(self, value):
+        self._bpm = int(value)
+        self._eci('cop-select 1')
+        self._eci('copp-select 1')
+        self._eci('copp-set', self._bpm)
+        self._redraw()
+
+    def stepBPM(self, dir, min=30, max=360):
+        step = abs(dir)
+        bpm = self._bpm/step * step
+        if self._bpm == bpm:
+            bpm += dir
+        elif dir>0:
+            bpm += 5
+        if bpm<min : bpm = min
+        if bpm>max : bpm = max
+        self.setBPM(bpm)
+
+    class VolumeSetter(object):
+
+        def __init__(self, metronome):
+            self._metronome = metronome
+
+        def __call__(self, value):
+            self._metronome.setVolume(value)
+
+    class VolumeGetter(object):
+
+        def __init__(self, metronome):
+            self._metronome = metronome
+
+        def __call__(self):
+            return self._metronome._volume
+            
+    def assignVolumeController(self, controller, title, min, max, steps=[]):
+        controller.assign(title,
+                          self.VolumeSetter(self),
+                          self.VolumeGetter(self),
+                          None,
+                          min, max, steps)
+
+    class BPMSetter(object):
+
+        def __init__(self, metronome):
+            self._metronome = metronome
+
+        def __call__(self, value):
+            self._metronome.setBPM(value)
+
+    class BPMGetter(object):
+
+        def __init__(self, metronome):
+            self._metronome = metronome
+
+        def __call__(self):
+            return self._metronome._bpm
+
+    def assignBPMController(self, controller, title, min, max, steps=[]):
+        controller.assign(title,
+                          self.BPMSetter(self),
+                          self.BPMGetter(self),
+                          None,
+                          min, max, steps)
+
+
+class Toggle(Actions.Action):
+
+    def __init__(self, name, m):
+        Actions.Action.__init__(self, name)
+        self._m = m
+
+    def __call__(self, binding):
+        self._m.toggle()
+
+class Tap(Actions.Action):
+
+    def __init__(self, name, m):
+        Actions.Action.__init__(self, name)
+        self._m = m
+
+    def __call__(self, binding):
+        self._m.tap()
+
+class AssignVolumeController(Actions.Action):
+
+    def __init__(self, name, metronome, controller, title, min, max, steps=[]):
+        Actions.Action.__init__(self, name)
+        self._metronome = metronome
+        self._controller = controller
+        self._title = title
+        self._min = min
+        self._max = max
+        self._steps = steps
+
+    def __call__(self, binding):
+        self._metronome.assignVolumeController(self._controller,
+                                               self._title,
+                                               self._min,
+                                               self._max,
+                                               self._steps)
+
+class AssignBPMController(Actions.Action):
+
+    def __init__(self, name, metronome, controller, title, min, max, steps=[]):
+        Actions.Action.__init__(self, name)
+        self._metronome = metronome
+        self._controller = controller
+        self._title = title
+        self._min = min
+        self._max = max
+        self._steps = steps
+
+    def __call__(self, binding):
+        self._metronome.assignBPMController(self._controller,
+                                            self._title,
+                                            self._min,
+                                            self._max,
+                                            self._steps)
+
+class StepBPM(Actions.Action):
+
+    def __init__(self, name, m, dir):
+        Actions.Action.__init__(self, name)
+        self._m = m
+        self._dir = dir
+
+    def __call__(self, binding):
+        self._m.stepBPM(self._dir)
+
+def register( viewmanager,
+              dispatcher,
+              context,
+              label,
+              x, y, dx, dy ):
+    metronome = Metronome(context, label, x, y, dx, dy, dispatcher)
+    viewmanager.registerView( metronome )
+    return metronome
index 98abe27..76ea832 100644 (file)
--- a/config.py
+++ b/config.py
@@ -4,9 +4,9 @@ from Actions import Actions as Action, action
 from Views import EventWidget
 from Events import Event
 import main
-import Joyboard, Keyboard, Process, TimeMachine, AlsaPlayer
+import Joyboard, Keyboard, Process, TimeMachine, AlsaPlayer, Metronome
 import Looper, Mixer
-import sys, curses, time, os
+import sys, curses, time, os, os.path, subprocess
 from curses.ascii import alt, ctrl
 
 def shift(letter) : return ord(chr(letter).upper())
@@ -20,45 +20,44 @@ global_map = Bindings.KeyMap()
 # Display size: 88x22
 
 #Logger.init(main.viewmanager, 38, 0, 37, 10, 'audiocontroller.log')
-Logger.init(main.viewmanager, 0, 17, 88, 5)
+Logger.init(main.viewmanager, 0, 17, 65, 5)
 
 jb = None
 ctl = None
-if os.path.exists('/dev/input/js0'):
-    jb = Joyboard.register(
-        viewmanager = main.viewmanager,
-        dispatcher = main.dispatcher,
-        
-        context          = 'jb0',
-        label            = 'Foot Switch',
-        numeric_switches =  10,
-        alpha_switches   =   5,
-        x                =   0,
-        y                =  10,
-        dx               =  88,
-        size             =   7,
-        
-        device           = '/dev/input/js0',
-        bits             = { 1:1, 3:2, 2:4, 0:8 },
-    )
-
-    ctl = Joyboard.registerController(
-        viewmanager = main.viewmanager,
-        dispatcher = main.dispatcher,
-        keylist = main.keylist,
-        source = jb,
-        
-        context          = 'c0',
-        name             = 'Control',
-        x                = 75,
-        y                =  0,
-        dx               = 13,
-        dy               = 10,
-
-        controller       = 0,
-        low              = -27200,
-        high             = -32700,
-    )        
+jb = Joyboard.register(
+    viewmanager = main.viewmanager,
+    dispatcher = main.dispatcher,
+    
+    context          = 'jb0',
+    label            = 'Foot Switch',
+    numeric_switches =  10,
+    alpha_switches   =   5,
+    x                =   0,
+    y                =  10,
+    dx               =  88,
+    size             =   7,
+    
+    device           = os.path.exists('/dev/input/js0') and '/dev/input/js0' or None,
+    bits             = { 1:1, 3:2, 2:4, 0:8 },
+)
+
+ctl = Joyboard.registerController(
+    viewmanager = main.viewmanager,
+    dispatcher = main.dispatcher,
+    keylist = main.keylist,
+    source = jb,
+    
+    context          = 'c0',
+    name             = 'Control',
+    x                = 75,
+    y                =  0,
+    dx               = 13,
+    dy               = 10,
+
+    controller       = 0,
+    low              = -27200,
+    high             = -32700,
+)        
 
 Keyboard.register(
     viewmanager = main.viewmanager,
@@ -91,12 +90,11 @@ for i,k in enumerate(('1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b'
     Action.register( Events.EmitEvent('key_%s' % k, main.dispatcher, Event('jb0', i)) )
     global_map.add( Binding( Event('kbd',key(k)), 'Foot %s' % k.upper(), Action['key_%s' % k] ) )
     
-if ctl is not None:
-    Action.register( Joyboard.StepController( 'controller_increment', ctl, +1 ) )
-    Action.register( Joyboard.StepController( 'controller_decrement', ctl, -1 ) )
+Action.register( Joyboard.StepController( 'controller_increment', ctl, +1 ) )
+Action.register( Joyboard.StepController( 'controller_decrement', ctl, -1 ) )
 
-    global_map.add( Binding( Event('kbd', curses.KEY_UP), 'Increment', Action['controller_increment'] ) )
-    global_map.add( Binding( Event('kbd', curses.KEY_DOWN), 'Decrement', Action['controller_decrement'] ) )
+global_map.add( Binding( Event('kbd', curses.KEY_UP), 'Increment', Action['controller_increment'] ) )
+global_map.add( Binding( Event('kbd', curses.KEY_DOWN), 'Decrement', Action['controller_decrement'] ) )
 
 Action.register( Actions.ChangeBindingsRelative( 'unset_this_map', 0, [] ) )
 
@@ -127,6 +125,9 @@ Action.register( Looper.Command('looper_reverse', looper, 'reverse') )
 Action.register( Looper.Command('looper_oneshot', looper, 'oneshot') )
 Action.register( Looper.Command('looper_undo_all', looper, 'undo_all') )
 Action.register( Looper.Command('looper_redo_all', looper, 'redo_all') )
+Action.register( Looper.Command('looper_mute_off', looper, 'mute_off') )
+Action.register( Actions.Macro( 'looper_trigger_unmute',
+                                [ 'looper_trigger', 'looper_mute_off' ] ) )
 
 looper_main_map.add ( Binding( Event('jb0',0),  'Rec',    Action['looper_record'] ) )
 looper_main_map.add ( Binding( Event('jb0',1),  'Over',   Action['looper_overdub'] ) )
@@ -147,35 +148,34 @@ looper_param_map = Bindings.KeyMap( 'Parameters' )
 Action.register( Actions.ChangeBindingsRelative('looper_set_param_map', 1, [looper_param_map] ) )
 looper_main_map.add ( Binding( Event('jb0',11), '[Param]', Action['looper_set_param_map'] ) )
 
-if ctl is not None:
-    Action.register( Looper.AssignController( 'looper_parm_rec_thresh', looper, ctl, 'Rec.Thresh.',
-                                              'rec_thresh', 0.0, 1.0 ) )
-    Action.register( Looper.AssignController( 'looper_parm_feedback', looper, ctl, 'Feedback',
-                                              'feedback', 0.0, 1.0 ) )
-    Action.register( Looper.AssignController( 'looper_parm_dry', looper, ctl, 'Dry Level',
-                                              'global_dry', 0.0, 1.0 ) )
-    Action.register( Looper.AssignController( 'looper_parm_wet', looper, ctl, 'Wet Level',
-                                              'global_wet', 0.0, 1.0 ) )
-    Action.register( Looper.AssignController( 'looper_parm_igain', looper, ctl, 'In. Gain',
-                                              'global_input_gain', 0.0, 1.0 ) )
-
-    steps = [ 1.0 ]
-    for i in range(6):
-        x = pow(2.0,2.0*(i+1)/12.0)
-        steps.append(x)
-        steps[0:0] = [1/x]
-
-    Action.register( Looper.AssignController( 'looper_parm_rate', looper, ctl, 'Rate',
-                                              'rate', 0.5, 2.0, steps ) )
-
-    looper_param_map.add( Binding( Event('jb0',5),  '(Feedb)', Action['looper_parm_feedback'] ) )
-    looper_param_map.add( Binding( Event('jb0',6),  '(Dry)',   Action['looper_parm_dry'] ) )
-    looper_param_map.add( Binding( Event('jb0',7),  '(Wet)',   Action['looper_parm_wet'] ) )
-    looper_param_map.add( Binding( Event('jb0',8),  '(Gain)',  Action['looper_parm_igain'] ) )
-    looper_param_map.add( Binding( Event('jb0',9),  '(Rec T)', Action['looper_parm_rec_thresh'] ) )
-    looper_param_map.add( Binding( Event('jb0',12), '',        Actions.Nop() ) )
-    looper_param_map.add( Binding( Event('jb0',13), 'Rev',     Action['looper_reverse'] ) )
-    looper_param_map.add( Binding( Event('jb0',14), '(Rate)',  Action['looper_parm_rate'] ) )
+Action.register( Looper.AssignController( 'looper_parm_rec_thresh', looper, ctl, 'Rec.Thresh.',
+                                          'rec_thresh', 0.0, 1.0 ) )
+Action.register( Looper.AssignController( 'looper_parm_feedback', looper, ctl, 'Feedback',
+                                          'feedback', 0.0, 1.0 ) )
+Action.register( Looper.AssignController( 'looper_parm_dry', looper, ctl, 'Dry Level',
+                                          'global_dry', 0.0, 1.0 ) )
+Action.register( Looper.AssignController( 'looper_parm_wet', looper, ctl, 'Wet Level',
+                                          'global_wet', 0.0, 1.0 ) )
+Action.register( Looper.AssignController( 'looper_parm_igain', looper, ctl, 'In. Gain',
+                                          'global_input_gain', 0.0, 1.0 ) )
+
+steps = [ 1.0 ]
+for i in range(6):
+    x = pow(2.0,2.0*(i+1)/12.0)
+    steps.append(x)
+    steps[0:0] = [1/x]
+
+Action.register( Looper.AssignController( 'looper_parm_rate', looper, ctl, 'Rate',
+                                          'rate', 0.5, 2.0, steps ) )
+
+looper_param_map.add( Binding( Event('jb0',5),  '(Feedb)', Action['looper_parm_feedback'] ) )
+looper_param_map.add( Binding( Event('jb0',6),  '(Dry)',   Action['looper_parm_dry'] ) )
+looper_param_map.add( Binding( Event('jb0',7),  '(Wet)',   Action['looper_parm_wet'] ) )
+looper_param_map.add( Binding( Event('jb0',8),  '(Gain)',  Action['looper_parm_igain'] ) )
+looper_param_map.add( Binding( Event('jb0',9),  '(Rec T)', Action['looper_parm_rec_thresh'] ) )
+looper_param_map.add( Binding( Event('jb0',12), '',        Actions.Nop() ) )
+looper_param_map.add( Binding( Event('jb0',13), 'Rev',     Action['looper_reverse'] ) )
+looper_param_map.add( Binding( Event('jb0',14), '(Rate)',  Action['looper_parm_rate'] ) )
 
 looper_param_map.add( Binding( Event('jb0',11), '[Main]',    Action['unset_this_map'] ) )
 
@@ -187,7 +187,7 @@ looper.set('sync',1)
 ###########################################################################
 # Mixer and effects
 
-mixer_map = Bindings.KeyMap('Mixer & Effects')
+mixer_map = Bindings.KeyMap('Master')
 Action.register( Actions.ChangeBindingsRelative( 'mode_mixer', 0, [ mixer_map ] ) )
 
 mixer = Mixer.register(
@@ -196,10 +196,10 @@ mixer = Mixer.register(
 
     context  = 'mix',
     label    = 'Mixer',
-    x        = 52,
-    y        =  0,
+    x        = 65,
+    y        = 17,
     dx       = 23,
-    dy       = 4,
+    dy       = 5,
 
     channels = ( 'Guitar', 'Voice' ),
     remote  = ('127.0.0.1', 9901),
@@ -232,7 +232,19 @@ tm = TimeMachine.register(
 
     ports   = ('minimixer:out_left', 'minimixer:out_right'),
     dir     = 'tm',
-    buffer  = 10)
+    buffer  =  1)
+
+metronome = Metronome.register(
+    viewmanager = main.viewmanager,
+    dispatcher  = main.dispatcher,
+
+    context = 'mt',
+    label   = 'Metronome',
+    x       = 52,
+    y       = 0,
+    dx      = 23,
+    dy      = 4,
+)
 
 Action.register( Mixer.AssignController  ( 'mixer_guitar_level', mixer, ctl, 'Guitar', 1 ) )
 Action.register( Mixer.ToggleMuteChannel ( 'mixer_mute_guitar',  mixer, 1 ) )
@@ -261,6 +273,26 @@ mixer_map.add( Binding( Event('jb0',14), '(Vol V)', Action['mixer_voice_level']
 
 mixer_map.add( Binding( Event('jb0',12), 'TM Rec',  Action['tm_rec_toggle'] ) )
 
+metronome_map = Bindings.KeyMap('Metronome')
+
+Action.register( Actions.ChangeBindingsRelative('set_metronome_map', 1, [metronome_map] ) )
+Action.register( Metronome.Toggle ( 'metronome_toggle', metronome ) )
+Action.register( Metronome.Tap ( 'metronome_tap', metronome ) )
+Action.register( Metronome.StepBPM ('metronome_bpm_inc', metronome, +5 ) )
+Action.register( Metronome.StepBPM ('metronome_bpm_dec', metronome, -5 ) )
+Action.register( Metronome.AssignVolumeController ( 'metronome_volume', metronome, ctl,
+                                                    "Metr Vol", 0.5, 9.9 ) )
+
+metronome_map.add( Binding( Event('jb0', 0), 'Tap', Action['metronome_tap'] ) )
+metronome_map.add( Binding( Event('jb0', 1), 'Toggle', Action['metronome_toggle'] ) )
+metronome_map.add( Binding( Event('jb0', 2), '(Vol)', Action['metronome_volume'] ) )
+metronome_map.add( Binding( Event('jb0', 3), 'bpm Dec', Action['metronome_bpm_dec'] ) )
+metronome_map.add( Binding( Event('jb0', 4), 'bpm Inc', Action['metronome_bpm_inc'] ) )
+
+metronome_map.add( Binding( Event('jb0', 11), '[Main]', Action['unset_this_map'] ) )
+
+mixer_map.add( Binding( Event('jb0', 11), '[Metr]', Action['set_metronome_map'] ) )
+
 mixer.mute(2)
 mixer.assignController( ctl, 'Guitar', 1 )
     
@@ -275,34 +307,74 @@ player = AlsaPlayer.Player()
 Action.register( AlsaPlayer.Play  ( 'player_start',  player ) )
 Action.register( AlsaPlayer.Pause ( 'player_pause',  player ) )
 Action.register( AlsaPlayer.Stop  ( 'player_stop',   player ) )
-Action.register( AlsaPlayer.Prev  ( 'player_prev',   player ) )
-Action.register( AlsaPlayer.Next  ( 'player_next',   player ) )
+Action.register( AlsaPlayer.Skip  ( 'player_skip_fwd', player, 5 ) )
+Action.register( AlsaPlayer.Skip  ( 'player_skip_rev', player, -5 ) )
 
 player_stop_map = Bindings.KeyMap()
 
 Action.register( Actions.ChangeBindingsRelative('player_set_stop_map', 1, [player_stop_map] ) )
-
-Action.register( Actions.Macro( 'player_set_playing', [ 'player_start', 'player_set_stop_map' ] ) )
-Action.register( Actions.Macro( 'player_set_stop',    [ 'player_stop', 'unset_this_map' ]       ) )
-
-for i in range(10):
+Action.register( Actions.Macro( 'player_set_playing',
+                                [ 'player_start', 'player_set_stop_map' ] ) )
+Action.register( Actions.Macro( 'player_set_stop',
+                                [ 'player_stop', 'unset_this_map' ]) )
+
+player_looper_map = Bindings.KeyMap()
+
+Action.register( Actions.ChangeBindingsRelative('player_set_looper_map', 1, [player_looper_map] ) )
+Action.register( Actions.Macro( 'player_start_loop_rec',
+                                [ 'looper_record', 'player_set_looper_map' ] ) )
+Action.register( Actions.Macro( 'player_stop_loop_rec',
+                                [ 'looper_record', 'player_pause', 'unset_this_map' ] ) )
+Action.register( Actions.Macro( 'player_looper_cancel',
+                                [ 'looper_undo', 'unset_this_map' ] ) )
+Action.register( Actions.Macro( 'player_stop_loop',
+                                [ 'looper_undo_all', 'player_pause' ] ) )
+
+for i in range(7):
     Action.register( AlsaPlayer.Jump  ( 'player_jump_%d' % i, player, i+1 ) )
     Action.register( Actions.Macro( 'player_set_track_%d' % i,
                                     [ 'player_jump_%d' % i, 'player_set_stop_map' ] ) )
-    player_map.add( Binding( Event('jb0',i), 'Tr %d' % (i+1), Action['player_set_track_%d' % i] ) )
+    player_map.add( Binding( Event('jb0',i+3), 'Tr %d' % (i+1), Action['player_set_track_%d' % i] ) )
 
-player_map.add( Binding( Event('jb0', 11), 'Prev',  Action['player_prev']        ) )
-player_map.add( Binding( Event('jb0', 12), 'Next',  Action['player_next']        ) )
-player_map.add( Binding( Event('jb0', 13), 'Play',  Action['player_set_playing'] ) )
-player_map.add( Binding( Event('jb0', 14), 'Pause', Action['player_pause']       ) )
+player_map.add( Binding( Event('jb0',  0), 'L Rec', Action['player_start_loop_rec'] ) )
+player_map.add( Binding( Event('jb0',  1), 'L Stp', Action['player_stop_loop'] ) )
+player_map.add( Binding( Event('jb0', 11), 'Back', Action['player_skip_rev'] ) )
+player_map.add( Binding( Event('jb0', 12), 'Fwd', Action['player_skip_fwd'] ) )
+player_map.add( Binding( Event('jb0', 13), 'Play', Action['player_set_playing'] ) )
+player_map.add( Binding( Event('jb0', 14), 'Pause', Action['player_pause'] ) )
 
 player_stop_map.add( Binding( Event('jb0', 13), 'Stop', Action['player_set_stop'] ) )
 
+player_looper_map.add( Binding( Event('jb0',  0), 'L Rec', Action['player_stop_loop_rec'] ) )
+player_looper_map.add( Binding( Event('jb0',  1), 'L Can', Action['player_looper_cancel'] ) )
+
+@action
+def player_queue_tm_recordings(binding):
+    global player
+    player.clear()
+    player.stop()
+    wavs = []
+    for f in os.listdir('tm'):
+        wav = os.path.splitext(f)[0] + '.wav'
+        if f.endswith('.w64'):
+            if not os.path.exists('tm/' + wav):
+                os.waitpid(subprocess.Popen(["sndfile-convert","-pcm32",f,wav],cwd='tm').pid,0)
+                wavs.append(wav)
+        elif f.endswith('.wav'):
+            wavs.append(f)
+    wavs.sort()
+    wavs.reverse()
+    for wav in wavs[:7]:
+        player.add('tm/' + wav)
+        player.stop()
+
+player_map.add( Binding( Event('jb0',  2), 'Add TM', Action['player_queue_tm_recordings'] ) )
+
 ###########################################################################
 
-mixer_map.add       ( Binding( Event('jb0',10), '[Loop]',   Action['mode_looper'] ) )
-looper_main_map.add ( Binding( Event('jb0',10), '[JBox]', Action['mode_player']   ) )
-player_map.add      ( Binding( Event('jb0',10), '[Mixer]',  Action['mode_mixer']  ) )
+mixer_map.add       ( Binding( Event('jb0',10), '[Loop]',  Action['mode_looper'] ) )
+looper_main_map.add ( Binding( Event('jb0',10), '[JBox]',  Action['mode_player'] ) )
+player_map.add      ( Binding( Event('jb0',10), '[Mastr]', Action['mode_mixer']  ) )
 
 main.keylist.append(global_map)
 main.keylist.append(mixer_map)
index f90e336..6a15566 100755 (executable)
--- a/start.sh
+++ b/start.sh
@@ -69,7 +69,7 @@ amixer sset 'Capture Mux' 0,1
 start 0 25 496 100 "JACK Audio Connection Kit [(default)] Started." \
     qjackctl
 
-sooperlooper -l 1 -c 1 -t 300 &
+sooperlooper -l 1 -c 2 -t 600 &
 clients="$clients $!"
 
 cd loops
@@ -83,11 +83,12 @@ start 0 153 496 428 "JACK Rack (voice) - voice.rack" \
 cd ..
 
 start 88 609 408 98 "AlsaPlayer" \
-    alsaplayer -r -l 0
+    alsaplayer -r -l 0 -d sooperlooper:common_in_1,sooperlooper:common_in_2
 clients="$clients $!"
 move 0 735 496 390 "Queue"
 alsaplayer --stop
-alsaplayer --volume 1
+alsaplayer --volume .25
+
 
 start 502 25 74 210 "dpm meter" \
     meterbridge -r 0 -t dpm -n meter alsa_pcm:capture_1 jack_rack_voice:out_1
@@ -107,8 +108,9 @@ sleep 1
 
 jack_connect alsa_pcm:capture_1 inputgain:in1_left
 jack_connect inputgain:out_left sooperlooper:common_in_1
+jack_connect inputgain:out_left sooperlooper:common_in_2
 jack_connect sooperlooper:common_out_1 minimixer:in1_left
-jack_connect sooperlooper:common_out_1 minimixer:in1_right
+jack_connect sooperlooper:common_out_2 minimixer:in1_right
 jack_connect alsa_pcm:capture_2 jack_rack_voice:in_1
 jack_connect jack_rack_voice:out_1 minimixer:in2_left
 jack_connect jack_rack_voice:out_1 minimixer:in2_right