A demo for recording in SCAMP with a few instruments

Hello everyone,

I modified the pygame file and added a few functions to control the rythm via binary trees.

To change the rhythm simply scroll the mouse button up at the given counter which represents an instrument defined at the beginning of the file.

1 = one note
2 = half note
4 = quarter note
etc.

I did a small recording of short session with musicxml:

My pc is unfortunately too slow for such realtime-processing.

The first eight counters are implemented as instruments.
You can add up to 48 instruments. At the moment there are only 8.
The octave is always the same and the volume also.
It would be nice to see, if you have any other ideas for user input with the counters.

Thanks for reading! :slight_smile:

Edit:
I did another recording showing some live composing capabilities of scamp:
score : https://musescore.com/user/37663311/scores/6678850
video: https://www.youtube.com/watch?v=Q5srGyZkykg
reddit - discussion: https://www.reddit.com/r/composer/comments/mb90oe/experimental_live_composing_with_python_scamp/

Hey – I finally tried out this code, and made a couple changes that I think you might find useful. First, I uncommented .run_as_server() in the line creating the session. This lets the session run on a separate thread. Then instead of putting the scamp stuff in the same loop as the pygame stuff, I put it in its own scamp_loop function, which I forked at the start of main(). In that scamp loop function, I do all the forking, waiting, etc. on the current clock (the one associated with scamp_loop), not on the session.

Not sure this will make sense without further explanation, but it runs without a hitch!

import pygame
from pygame.locals import *
import random


from scamp import Session, Ensemble, current_clock

def construct_ensemble():
    global piano_clef,piano_bass, flute, strings, session
    ensemble = Ensemble(default_soundfont="/usr/share/sounds/sf2/FluidR3_GM.sf2")

    #ensemble.print_default_soundfont_presets()

    second = ensemble.new_part("Violoncello")
    first = ensemble.new_part("Violin") #("violoncello")
    return [first,second,ensemble.new_part("Piano"),ensemble.new_part("Piano"),ensemble.new_part("Panflute"),ensemble.new_part("Harp")]
    #strings = ensemble.new_part("strings", (0, 40))

def aT(u,a):
    if u in [1,5,7,11]:
        return lambda x : (u*x+a)%12

def mul(U,V):
    u,a = U
    x,y = V
    return ((u*x)%12,(u*y+a)%12)

def iterMul(x,k):
    if k == 1:
        return x
    else:
        return mul(iterMul(x,k-1),x)

def orderMul(x):
    y = x
    o = 1
    while y!=(1,0):
        o+=1
        y = mul(y,x)
    return o



pygame.init()
screen = pygame.display.set_mode((640, 480))
clock = pygame.time.Clock()

def drawRect(surface,color,xStart,yStart):
    pygame.draw.rect(surface, color, Rect(xStart,yStart,80,80),0)


def drawText(surface, text, x,y):
    textsurface = myfont.render(text, False, (255,255,255))
    surface.blit(textsurface,(x,y))

def getRect(x,y):
    k = (x//80)
    l = (y//80)
    return k+l*8

pygame.font.init() # you have to call this at the start, 
                  # if you want to use this module.
myfont = pygame.font.SysFont('Comic Sans MS', 30)
#This creates a new object on which you can call the render method.


oneOctave = list(range(60,72))
bassOctave = list(range(60-1*12,60-0*12))
startPitch = 0 
startPitchBass = 1
affineGroup = [aT(u,a) for u in [1,5,7,11] for a in range(12)]  
affineGroupIndex = [(u,a) for u in [1,5,7,11] for a in range(12)]  

affineGroupByOrder = [ (orderMul(x),x) for x in affineGroupIndex]
print(affineGroupByOrder)
twoLoops = [x for o,x in affineGroupByOrder if o==2]
threeLoops = [x for o,x in affineGroupByOrder if o==3]
fourLoops = [x for o,x in affineGroupByOrder if o==4]
print(len(twoLoops))
print(len(fourLoops))
countBass = 0


s = Session(tempo=120,default_soundfont="/usr/share/sounds/sf2/FluidR3_GM.sf2").run_as_server()

s.print_available_midi_output_devices()

tracks = construct_ensemble()

for t in tracks:
    s.add_instrument(t)


counters =  dict(zip(range(48),48*[0]))

def updateCounterForRect(rectangleNumber,plusOne=True):
    global counters
    if plusOne:
        counters[rectangleNumber] += 1
    else:
        counters[rectangleNumber] -= 1


def digits(n,base,padto=None):
    q = n
    ret = []
    while q != 0:
        q, r = q//base,q%base # Divide by 10, see the remainder
        ret.insert(0, r) # The remainder is the first to the right digit
    if padto is None:
        return ret
    for i in range(padto-len(ret)):
        ret.insert(0,0)
    #ret.extend((padto-len(ret))*[0])    
    return ret

def repeatingNumbers(dd,D,offset=1,NStart=1,NEnd = 100):
    return [sum([n//d for d in dd])%D+offset for n in range(NStart,NEnd+1)]

def durationMingus2Music21(mingusDuration):
    if float(mingusDuration) == 0.0:
        return None
    else:
        return float(1/mingusDuration)
    
def durationMingus2MidiUtil(mingusDuration)    :
    if float(mingusDuration) == 0.0:
        return None
    else:
        return float(1/mingusDuration*4) # beats per minutes in quarternotes

def digitsReversed(n,base,padto):
    q = n
    ret = []
    while q != 0:
        q, r = q//base,q%base # Divide by 10, see the remainder
        ret.insert(0, r) # The remainder is the first to the right digit
    if padto is None:
        return ret
    for i in range(padto-len(ret)):
        ret.insert(0,0)
    #ret.extend((padto-len(ret))*[0])    
    return ret


def sumTree(n,leftToRight=True):
    if n==1:
        return []
    else:
        if leftToRight:
            return [sumTree(int(n//2),leftToRight),sumTree(n-int(n//2),leftToRight)]
        else:
            return [sumTree(n-int(n//2),leftToRight),sumTree(int(n//2),leftToRight)]


def digitsTree(n):
    if n==0:
        return []
    dd = digits(n-1,2)
    dd.reverse()
    #print(dd)
    ll = []
    oo = [dd[i] for i in range(len(dd)) if i%2==1]
    ee = [dd[i] for i in range(len(dd)) if i%2==0]
    #print(oo,ee)
    O = sum([2**(i)*oo[i] for i in range(len(oo))])
    E = sum([2**(i)*ee[i] for i in range(len(ee))])
    #print(O,E)
    return [digitsTree(O),digitsTree(E)]


def getDurationsFromTree(tree):
    # Identify leaves by their length
    if len(tree) == 0:
        return [1]
    # Take all branches, get the paths for the branch, and prepend current
    dd = []
    for t in tree:
        dd.extend([2*d for d in getDurationsFromTree(t)])
    return dd 

def getDottedDurationsFromTree(tree,dotted=True):
    if len(tree)==0:
        return [1]
    dd = []
    if (len(tree[0])==0 or len(tree[1])==0) and dotted:
        dd.extend([4/3*d for d in getDottedDurationsFromTree(tree[0],dotted)])
        dd.extend([4*d for d in getDottedDurationsFromTree(tree[1],dotted)])
    else:
        for t in tree:
            dd.extend([2*d for d in getDottedDurationsFromTree(t,dotted)])
    return dd


def generateBar(nTracks,barNumber,notelist,SYMFUNC,NFUNC,BASEFUNC):
    bars = []
    for i in range(nTracks):
        bars.append([])
        
    for tt in range(nTracks):
        barCounter = 0
        c = 1
        for bb in [barNumber]:
            K = bb[tt]
            mingusDurations = getDurationsFromTree(digitsTree(max(K,1)))
            durations = mingusDurations
            pitches = []
            volumes = []
            c = bb[tt]
            barCounter += 1
            for d in durations:
                c+= 1
                dc = digitsReversed(c,BASEFUNC,padto=NFUNC)[:NFUNC]
                pitchMod = (SYMFUNC([d+1 for d in dc]))%len(notelist)
                pitchlist = []
                if K>0:
                    pitch = notelist[pitchMod]
                else:
                    pitch = None
                pitches.append([pitch])
                volumes.append(0.1*bb[tt])    # todo: change this
            bar = list(zip(pitches,durations,volumes))        
            bars[tt].append(bar)    
    return(bars)

import math
SYMFUNC = lambda a : int(a[0]*a[1]*(a[0]+a[1])/int(math.gcd(a[0],a[1])**3))
NFUNC = 2
BASEFUNC = 19


def play_bar_for_instrument(instNr,bar):
    global tracks, counters
    if counters[instNr]<=0:
        return
    print(instNr,bar)
    for i in range(len(bar)):
        nc,duration,volume = bar[i]
        dur = 4.0/(duration)
        pitch = nc[0]
        if not pitch is None:
            tracks[instNr].play_note(pitch,volume, dur)

def scamp_loop():
    global countBass,tracks,oneOctave,SYMFUNC,NFUNC,BASEFUNC, counters
    while True:
        nTracks = len(tracks)
        barNumbers = [counters[k]  for k in range(nTracks)]
        bars = generateBar(nTracks, barNumbers, oneOctave, SYMFUNC,NFUNC,BASEFUNC)
        for tt in range(len(tracks)):
            for t in range(len(bars[tt])):
                if counters[tt]>0:
                    current_clock().fork(play_bar_for_instrument,(tt,bars[tt][t]))
        if len(current_clock().children()) > 0:
            current_clock().wait_for_children_to_finish()
        else:
            # prevents hanging if nothing has been forked yet
            current_clock().wait(4.0)

def main():
   global countBass,tracks,oneOctave,SYMFUNC,NFUNC,BASEFUNC, counters
   s.fork(scamp_loop)
   while True:
      for event in pygame.event.get():
            xPos,yPos = (pygame.mouse.get_pos())
            leftPressed,middlePressed,rightPressed = pygame.mouse.get_pressed()
            #print(xPos,yPos,leftPressed,rightPressed)
            rect = getRect(xPos,yPos)
            if event.type == pygame.MOUSEBUTTONDOWN:
                if event.button == 4: 
                    updateCounterForRect(rect,True)
                if event.button == 5:
                    updateCounterForRect(rect,False)
            if leftPressed:
                updateCounterForRect(rect,True)
            if rightPressed:
                updateCounterForRect(rect,False)
            #print(counters)
            if event.type == QUIT:
               #pygame.quit()
               return
               #print(event)
               #print(event.x, event.y)
               #print(event.flipped)
               #print(event.which)
               # can access properties with
               # proper notation(ex: event.y)
      for k in range(8):
          for l in range(6):
              color = ((k*5)%255,(l*5)%255,(k+l)%255)
              drawRect(screen, color,k*80,l*80)
              drawText(screen, str(counters[k+l*8]), k*80+40,l*80+40)
      pygame.display.flip()
      clock.tick()
# Execute game:
s.start_transcribing()
main()
performance = s.stop_transcribing()
score = performance.to_score()
music_xml = score.to_music_xml()
music_xml.export_to_file("my_first_music_xml.xml")
1 Like

Thanks Marc,

I will try it out as soon as I have some time!

Kind regards.

Great work Marc! Thank you for your help! It runs very smooth now.

Kind regards.

Of course!

By the way, it’s worth mentioning that if you import wait, wait_for_children_to_finish, etc. directly from scamp, as in:

from scamp import wait, wait_for_children_to_finish

or just

from scamp import *

Then these functions are aliases to current_clock().wait and current_clock().wait_for_children_to_finish.