Real-time list of midi messages into SCAMP script

Hi SCAMPsters! Just a quick question about the MIDI flow within this script I’m using. Ive recently been building on my collection of compositional tools when I came across the isorhythm script built by Marc Evanstein using SCAMP…

To summarise, I have midi messages being sent from my DAW (Reaper) into the script, the script then takes these messages and makes a list of 6 midi notes which then get sent to the “do_isorhythm” function that is being forked later on in the script. The problem is, despite defining the list and inputting it into the “args=” of the “do_isorhythm” fork it just isn’t being registered.

Im assume it could be a few things but my main guess is the timing of the script and that the midi information isn’t being passed to the function in time/sync, here’s the code:

from scamp import *
from collections import namedtuple
import datetime
from itertools import cycle
import random
from random import randint


USE_PLAY_NOTE=False
s = Session()
t = Session(tempo=140)
inst_1 = s.new_midi_part("melo-1", 0)

class Manipulator:
    def __init__(self):
        self.session = Session()
        self.session.print_available_midi_output_devices()
        self.session.print_available_midi_input_devices()
        try:
            self.piano = self.session.new_midi_part("piano", midi_output_device=0, num_channels=8) # Scamp to QSynth
        except:
            print("Failed to open MidiLoop Port for output")      

        self.midi_messages = []  # List to store MIDI pitch values
        self.message_count = 0    # Counter for MIDI messages
        self.count_started = False  # Flag to track if the count has started

    def midi_callback(self, midi_message):
        code, pitch, volume = midi_message
        
        if pitch == 0 and volume > 0:  # Start the count when MIDI message "0" is received
            self.count_started = True

        if self.count_started:
            if volume > 0:  # Only append the pitch value if it's an "on" message
                self.midi_messages.append(pitch)  # Append the pitch value to the list
                self.message_count += 1

                if self.message_count == 4:
                    # Insert "None" randomly into the list at two random positions
                    for _ in range(2):
                        index = random.randint(0, len(self.midi_messages))
                        self.midi_messages.insert(index, None)
                    
                    # Print the captured MIDI messages in the desired format
                    print("Captured MIDI messages:")
                    print(self.midi_messages)
                    # Reset counter and clear the list
                    self.message_count = 0
                    self.midi_messages.clear()
            if volume > 0:  # This is an on-event
                self.onTime = datetime.datetime.now()
                self.note = Note(pitch, volume, self.onTime.timestamp())
   
            else:  
                self.offTime = datetime.datetime.now()
                delta = self.offTime - self.onTime
                self.note.duration = float(delta.total_seconds())

                # Eventually do something with the note here
                if USE_PLAY_NOTE:
                    self.piano.play_note(self.note.pitch, .8, self.note.duration) 
                else:
                    noteReference = self.piano.start_note(self.note.pitch, .8)
                    wait(self.note.duration)
                    noteReference.end()

    def Run(self):
        def do_isorhythm(inst_1, color, talea, voluminor=(1.0,)):
            for pitch, volume, dur in zip(cycle(color), cycle(voluminor), cycle(talea)):
                inst_1.play_note(pitch, volume, dur)
        
        try:     
            self.session.register_midi_listener(1, self.midi_callback) #[Port 4]: Input from Virtual Keyboard
        except:
            print("Failed to open MidiLoop Port for input")                
        #self.session.wait_forever()
        s = Session()
        t = Session(tempo=140)
        global_midi = self.midi_messages
        # Call do_isorhythm within Run
        fork(do_isorhythm, args=(inst_1, [64, 62, None, 65, 67, None, 62],
                     [1.0, 0.5, 0.75, 0.25, 1.0], [0.5, 0.7, 0.3]))
        fork(do_isorhythm, args=(inst_1, [None, 64, 67, 69, None],
                     [2.0, 0.5, 1.0], [0.3, 0.5, 0.4, 0.6, 0.9, 0.6, 0.4]))

m = Manipulator()
m.Run()
wait_forever() 

Currently, the fork function looks like this:

        fork(do_isorhythm, args=(inst_1, [64, 62, None, 65, 67, None, 62],
                     [1.0, 0.5, 0.75, 0.25, 1.0], [0.5, 0.7, 0.3]))
        fork(do_isorhythm, args=(inst_1, [None, 64, 67, 69, None],
                     [2.0, 0.5, 1.0], [0.3, 0.5, 0.4, 0.6, 0.9, 0.6, 0.4]))

I’m trying to make it work like this, but having problems:

         fork(do_isorhythm, args=(inst_1, global_midi,
                     [1.0, 0.5, 0.75, 0.25, 1.0], [0.5, 0.7, 0.3]))
        fork(do_isorhythm, args=(inst_1, global_midi,
                     [2.0, 0.5, 1.0], [0.3, 0.5, 0.4, 0.6, 0.9, 0.6, 0.4]))

This way my live midi input gets sent into the isorhythm function and then outputs it back into my DAW for later processing, any insight into how I can achieve this??

Im new here so please do forgive me if my query hasn’t been formatted to this forum’s standards… any help/guidance is much appreciated!!

Thanks in advance <3

Hi! I’m confused about why you’re creating so many different session objects. There should really only ever by a need to create one Session object.

1 Like

Ahh for some reason when I initiate the m.run(), if “session” isn’t called just before the fork, the tempo doesn’t get registered. The code itself will get cleaned up it’s just right now I’m trying to focus on getting the process I mentioned sorted out, any constructive help is welcome! Thanks

It’s just hard for me to make sense of with all those session objects. Also, since you’re trying to do something that’s reacting in real time, you should do:

s = Session().run_as_server()

And then any calls to fork should be s.fork instead of just fork.

Try rewriting it that way, and see if it helps, and if not, I can take another look

1 Like

Thank you so much for your advice, I’ve implemented it but for some reason, the tempo isn’t being registered, I’ll have another look tonight and get back to you!

I think I’ve noticed that it doesn’t work to set the tempo on a session that’s running as server. But there are different ways to set the tempo of a forked process. Want to post the code?

1 Like