Skip to main content

MidiScheduler — wiki

Musical note-event scheduler

Simplifies making musical rhythms and chords in pygame. It combines the start and end timing of the note from separate midi-on midi-off commands to one note-event.

## Pauli Laine December 2010 
## Distribute and modify freely

import pygame,random,time
import pygame.midi
from pygame.locals import *
pygame.init()                         #initialize
time.sleep(0.2)
pygame.midi.init()
output_id = pygame.midi.get_default_output_id()
print 'output', output_id
MidiOut = pygame.midi.Output(0,0)


class Scheduler:
    def __init__(self):
        ## Here we have a small scheduling ring buffer with 4 bars each with 192 resolution
        ## 192 resolution seems to be good enough for normal purposes
        self.sixe = int(192 * 4)
        self.onlist = []
        ## Note_ons and note_offs in separate lists makes sorting non-necessary
        for n in range(self.sixe):
            tmp = []
            self.onlist.append(tmp)  
        self.offlist = []
        for n in range(self.sixe):
            tmp = []
            self.offlist.append(tmp)

    def insertnote(self,note):
        posit = note.startp % self.sixe
        tmpnode = self.onlist[posit % len(self.onlist)]
        tmpnode.append(note)
        self.onlist[posit] = tmpnode

    def insertnoteoff(self,note):
        posit = note.endp % self.sixe
        tmpnode = self.offlist[posit % len(self.onlist)]
        tmpnode.append(note)
        self.offlist[posit] = tmpnode

    def putnote(self,note):
        posit = note.startp % self.sixe
        endposit = note.endp % self.sixe
        self.insertnote(note)
        self.insertnoteoff(note)

    def handletick(self,posit):
        posit %= self.sixe
        tmpoff = self.offlist[posit]
        tmpon = self.onlist[posit]
        self.onlist[posit] =  []
        self.offlist[posit] =  []        
        self.playticks(tmpoff,tmpon)

    def playticks(self,noteoffs, noteons):
        ##Noteoffs first so as to avoid 'insta_kill' of a unisono note
        for note in noteoffs:
            MidiOut.write([[[0x90 + note.chan,note.pitch,0],pygame.midi.time()]])

        for note in noteons:
            MidiOut.write([[[0x90 + note.chan,note.pitch,note.dyn],pygame.midi.time()]])



## Notes with same startp will play simultaneously, as chords
## Note.endp controls the duration of the note
## startp and endp are 'ticks' - time interval controlled by
## pygame.time.wait() between calls to scheduler.handletick
            
class Note:
    def __init__(self):
        self.startp,self.endp = 0,0
        self.dyn,self.chan = 65,0
        self.pitch = 60


## Make a scheduler instance
s = Scheduler()


def testmidi():
    globtick = 0
    for k in range(121):

        if (k % 3 == 0):
            n = Note()
            testpitch = k % 7 + (k / 7) ## just some mod-stuff to add variety to pitches
            n.pitch = 34 + ((testpitch + 30) % 60)

            ## Note starttime and endtime
            n.startp = globtick + (1 + (3  * (k % 8))) ## some variety to rhythms
            n.endp = globtick + (8 + (3 * (k % 8)))    ## listen for chords

            ## Note is appended to scheduler to a place determined by note.startp
            s.putnote(n)   

        ## Handle Note.startp's and endp's positioned in current tick
        s.handletick(globtick)

        globtick += 1
        ## Adjust this to control tempo
        pygame.time.wait(20)
        
    ## Ensure that all notes are ended 
    for k in range(23):
        s.handletick(globtick)
        globtick += 1
        pygame.time.wait(20)

import random
def testmidi_random():
    globtick = 0
    for k in range(121):

        n = Note()
        n.pitch = random.randint(36,72)

        ## Note starttime and endtime
        stara = random.randint(1,12)
        dura = random.randint(2, 8)
        n.startp = globtick + stara
        n.endp = globtick + stara + dura
        n.dyn = random.randint(30,90)
        s.putnote(n)
            

        ## Handle Note.startp's and endp's positioned in current tick
        s.handletick(globtick)

        globtick += 1
        ## Adjust this to control tempo
        pygame.time.wait(180)
        
    ## Ensure that all notes are ended 
    for k in range(23):
        s.handletick(globtick)
        globtick += 1
        pygame.time.wait(80)


## testmidi()
testmidi_random()