Alternating record/play for rehearsal.

Discuss using the OSC interface to control SL

Moderator: jesse

Post Reply
Michael Ellis
Posts: 3
Joined: Fri Oct 14, 2011 4:17 pm

Alternating record/play for rehearsal.

Post by Michael Ellis »

Noob here, first post.

I just downloaded and installed SL and JACK onto my MacBook (OS-X 10.6.7). Everything worked very smoothly (not always the case with open source audio apps on OS-X, so congrats for the good development work!) and I'm now able to record and play back through the system sound devices.

My intended use is very simple but I suspect it will require some programming. I want to put SL into a hands-free mode where it alternates (in continuous rhythm) between recording and playback for a fixed duration, e.g. one or two bars at some desired tempo, e.g. I play a phrase on my violin, SL plays it back, I play it again, etc.

If this can be done without programming, hooray, but if not programming is my day job and I'll be happy to share whatever I cobble together. I saw in another post here that there may be a Python interface. If so, that would be -- well, sooper.

Any advice appreciated.

Thanks,
Mike

Michael Ellis
Posts: 3
Joined: Fri Oct 14, 2011 4:17 pm

Re: Alternating record/play for rehearsal.

Post by Michael Ellis »

I've coded a mock-up of the program I want to create for alternating play/listen rehearsal. Sorry, no GUI -- I want to spend more time playing and less time coding :-). The current help output with examples is shown below. Feedback welcomed. I tried to aim for the simplest interface that made sense musically while being consistent with command line conventions. I've also gotten liblo and pyliblo installed and looked at some of the work posted here by others, (very useful, thanks!) but would welcome advice on getting past the "hello liblo" stage and, if possible, some thoughts on which SnooperLooper OSC commands would be required to support a real application.

Thanks,
Mike
$ slpractice.py -h
usage: slpractice.py [-h] [-t TEMPO] [-r REPEATS] [-c COUNT_IN]
[BARSPEC [BARSPEC ...]]

OSC controlled interface to SooperLooper for alternating record/listen.
Supports multiple measure loops with changes of meter and tempo.

positional arguments:
BARSPEC The number of beats in the bar optionally followed by
a relative or absolute tempo indicator e.g. 4*90 means
4 beats at 90% of the baseline tempo whereas 4@90
means 4 beats at 90 bpm. Tempo reverts to baseline at
the end of each bar.

optional arguments:
-h, --help show this help message and exit
-t TEMPO, --tempo TEMPO
baseline tempo in beats per minute (default: 120)
-r REPEATS, --repeats REPEATS
number of repetitions of the record/listen cycle
(default: 8)
-c COUNT_IN, --count_in COUNT_IN
number of measures for the initial count-in. (default:
2). Tempo and meter for count-in measures are taken
from the first BARSPEC

EXAMPLES
slpractice.py (no arguments)
Performs a two bar count-in (4 beats per bar) followed by
8 record/listen cycles of 1 bar at 120 beats per minute.
Terminal output appears as shown below with each count printed
in correct rhythm, ie as a visual metronome.

COUNT-IN
1 2 3 4
1 2 3 4

PLAY
1 2 3 4

LISTEN
1 2 3 4

PLAY
1 2 3 4

LISTEN
1 2 3 4

<< 6 more cycles >>

slpractice.py 4 4*90 4
Performs cycles of 3 bars with a 10% ritard in the second bar
and an 'a tempo' in the third. As in the previous example
there is a two bar count-in followed by 8 record/listen cycles.
Baseline tempo is 120 beats per minute.

slpractice.py -c1 -t90 -r4 2 3*150
Performs a 1 bar count_in of two beats at 90 bpm, then
4 cycles of a 2-beat bar at 90 bpm and a 3-beat bar at 135 bpm.
Note that this example is contrived so that both measures have
the same duration (as if changing between 6/8 and 3/4 time).

The same result could be obtained in other ways, including:
slpractice.py -c1 -t90 -r4 2 3@135
slpractice.py -c1 -t135 -r4 2*66.7 3

Michael Ellis
Posts: 3
Joined: Fri Oct 14, 2011 4:17 pm

Re: Alternating record/play for rehearsal.

Post by Michael Ellis »

Here is a working version of the script proposed in the last post. I think I've put everything you need to know into the help. Works nicely on my vintage 2008 white Macbook under OS X 10.6.7. Drift (displayed at end of run) is typically 30 ~ 40 msec in 32 seconds of interaction (16 bars at 4=120). That's close enough for my purposes, but I think it could be tightened up with a more sophisticated calculation of the sleep time after each beat -- or possibly by using callbacks instead of a simple loop.

In using it, I found it helpful to set my terminal window to a large font to make the counts easier to see from several feet away.

Feedback and suggestions for improvement welcome.

Enjoy,
Mike

Code: Select all

#!/usr/bin/env python
"""
Author: Michael Ellis
Copyright 2011 Ellis & Grant, Inc.
License: GPL2, Warranty: Absolutely none.

OSC control interface to SooperLooper for alternating record/listen. 
Supports multiple measure loops with changes of meter and tempo.

Requirements:
1. This program uses the python argparse module which is new in
python 2.7. 
2. You need to have a working instance of the SooperLooper engine
running and connected to your desired inputs and outputs before
running this script. 
3. You also need liblo and pyliblo installed.

For more information about SooperLooper, visit 
http://www.essej.net/sooperlooper/

"""
import sys
import os
import argparse
import time

import liblo

_usage_examples = """
EXAMPLES
slpractice.py (no arguments)
Performs a two bar count-in (4 beats per bar) followed by 
8 record/listen cycles of 1 bar at 120 beats per minute.
Terminal output appears as shown below with each count printed
in correct rhythm, ie as a visual metronome.

COUNT-IN
1 2 3 4 
1 2 3 4 

PLAY
1 2 3 4 

LISTEN
1 2 3 4 

PLAY
1 2 3 4 

LISTEN
1 2 3 4 

<< 6 more cycles >>

slpractice.py 4 4*90 4
Performs cycles of 3 bars with a 10% ritard in the second bar
and an 'a tempo' in the third. As in the previous example
there is a two bar count-in followed by 8 record/listen cycles.
Baseline tempo is 120 beats per minute.

slpractice.py -c1 -t90 -r4  2 3*150
Performs a 1 bar count_in of two beats at 90 bpm, then
4 cycles of a 2-beat bar at 90 bpm and a 3-beat bar at 135 bpm.
Note that this example is contrived so that both measures have
the same duration (as if changing between 6/8 and 3/4 time).

The same result could be obtained in other ways, including:
slpractice.py -c1 -t90 -r4  2 3@135
slpractice.py -c1 -t135 -r4  2*66.7 3

"""

## ----------------------------------------------------------------------------
## Command line parser
## ----------------------------------------------------------------------------

_parser = argparse.ArgumentParser(description=__doc__,
                                epilog = _usage_examples,
                                formatter_class=argparse.RawDescriptionHelpFormatter)

_parser.add_argument('measures', metavar='BARSPEC', type=str, nargs='*',
                   default="4",
                   help='The number of beats in the bar optionally '\
                        'followed by a relative or absolute tempo indicator '\
                        'e.g. 4*90 means 4 beats at 90%% of the baseline '\
                        'tempo whereas 4@90 means 4 beats at 90 bpm. '\
                        'Tempo reverts to baseline at the end of each bar.')
                       

_parser.add_argument('-t','--tempo', type = int, action = 'store',
                   default = 120, 
                   help = "baseline tempo in beats per minute "\
                   "(default: 120)")

_parser.add_argument('-r','--repeats', type = int, action = 'store',
                   default = 8, 
                   help = 'number of repetitions of the '\
                          'record/listen cycle (default: 8)')

_parser.add_argument('-c','--count_in', type = int, action = 'store',
                   default = 2, 
                   help = 'number of measures for the initial count-in. '\
                          '(default: 2). Tempo and meter for count-in '\
                          'measures are taken from the first BARSPEC')

_parser.add_argument('-p','--osc-port', type = int, action = 'store',
                    default = 9951,
                    help = 'Port number on which SooperLooper, (SL), is listening. ' \
                           '(default: 9951) The default is the standard port ' \
                           'when operating standalone.')  


def parse_barspec(s, args):
    """
    Handle one barspec. If it is valid, return a tuple of (beat, tempo),
    otherwise print the barspec and raise SystemExit.
    """
    try:
        if '*' in s:
            beats,relative_tempo = s.split('*')
            tempo = float(relative_tempo) * args.tempo / 100. 
        elif '@' in s:
            beats, tempo = s.split('@')
            tempo = float(tempo)
        else:
            beats = s
            tempo = float(args.tempo)
        beats = int(beats)
    except ValueError:
        print "Bad BARSPEC: %s" % s
        raise SystemExit

    return beats,tempo    

## ----------------------------------------------------------------------------
## Utility Functions
## ----------------------------------------------------------------------------
def count_bar(beats, tempo):
    """ Visual metronome for terminal display. """
    beat_length = 60.0/tempo
    lis = ["%d" % (i+1) for i in xrange(beats)]
    # print " ".join(lis)
    for n in lis:
        sys.stdout.write("%s " % n)
        sys.stdout.flush()
        time.sleep(beat_length)

    sys.stdout.write("\n")
    


def run():
    """ 
    The 'main' function for this script. Parses the command line args,
    creates the list of measure specs, displays count-in, then enters
    the play/listen loop for the number of repeats requested on the
    command line.

    Side effects:
    Changes state of SooperLooper server including overwriting contents
    of loop 0.
    """

    args = _parser.parse_args()
    target = liblo.Address(args.osc_port)

    allbars = []
    for bar in args.measures:
        allbars.append(parse_barspec(bar, args))
    
    expected_duration = 2 * args.repeats * sum([b[0]*60./b[1] for b in allbars]) 
    total_bars = 2 * args.repeats * len(allbars)

    #TODO init, including stop any playback or recording.

    print "COUNT-IN"
    for i in xrange(args.count_in):
        count_bar(*allbars[0])
    start = liblo.time()
    for r in xrange(args.repeats):
        rno = r + 1
        # Start recording
        liblo.send(target, '/sl/0/hit', 'record') 
        print "\n%d. PLAY" % rno
        for bar in allbars:
            count_bar(*bar)
        # At end of last beat, start playback in 'once' mode  
        liblo.send(target, '/sl/0/hit', 'oneshot')     
        print "\n%d. LISTEN" % rno
        for bar in allbars:
            count_bar(*bar)
    #TODO any necessary cleanup before exiting.

    end = liblo.time()   
    actual_duration = end - start
    print
    print "Expected duration: %.3f s." % expected_duration 
    print "Actual duration: %.3f s." % actual_duration 
    print "Timing drift = %0.2f seconds in %d bars" % \
            (actual_duration - expected_duration, total_bars)

## ----------------------------------------------------------------------------
if __name__ == '__main__':
    run()                                                                                                        

Post Reply