Loopy Pro: Create music, your way.

What is Loopy Pro?Loopy Pro is a powerful, flexible, and intuitive live looper, sampler, clip launcher and DAW for iPhone and iPad. At its core, it allows you to record and layer sounds in real-time to create complex musical arrangements. But it doesn’t stop there—Loopy Pro offers advanced tools to customize your workflow, build dynamic performance setups, and create a seamless connection between instruments, effects, and external gear.

Use it for live looping, sequencing, arranging, mixing, and much more. Whether you're a live performer, a producer, or just experimenting with sound, Loopy Pro helps you take control of your creative process.

Download on the App Store

Loopy Pro is your all-in-one musical toolkit. Try it for free today.

Anyone coding Mozaic here that I can chat with?

I've got a plan that I need to write a moderately complex Mozaic script for, however LLMs seem to have gotten a lot worse at it since last time I was developing with it (and it was pretty bad then too, with most syntax being invalid).

I'm going to figure it out/learn myself as it's some pretty exciting utility that's related to interfacing with other music based code I've written (https://github.com/OscarSouth/theHarmonicAlgorithm).

Anyone out there who's into Mozaic script that I could chat with and bounce ideas off while I develop?

Let me know :)

Comments

  • I'm up for it. I'm not always the best coder, not being trained as a programmer, but I have a fairly good grasp of Mozaic.

  • edited December 2024

    I'd be impressed if Mozaic can do this, I'd love to know if it could. While not attempting anything this ambitious, I've run into limitations with Mozaic with its math functions, memory options - arrays are somewhat limited, and the length of scripts. Suggest you take a look through the manual: https://ruismaker.com/manuals/mozaic.pdf

    One of the things i do to mitigate the script limits and overall complexity in a given script is to use midi cc to trigger actions in other scripts, but the other stuff is harder to mitigate. Good luck!

  • @wim said:
    I'm up for it. I'm not always the best coder, not being trained as a programmer, but I have a fairly good grasp of Mozaic.

    Do you know if there's anything like a discord for mozaic? The help chat is pretty good actually. Want to get better at it so basically looking for places/people to engage in discourse with in the process of improving.

  • @belldu said:
    I'd be impressed if Mozaic can do this, I'd love to know if it could. While not attempting anything this ambitious, I've run into limitations with Mozaic with its math functions, memory options - arrays are somewhat limited, and the length of scripts. Suggest you take a look through the manual: https://ruismaker.com/manuals/mozaic.pdf

    One of the things i do to mitigate the script limits and overall complexity in a given script is to use midi cc to trigger actions in other scripts, but the other stuff is harder to mitigate. Good luck!

    Cheers :)

    Have read/forgot/reread the manual a few times over the years. I reckon what I'm planning to do will be pretty easily doable -- my Haskell codebase linked there is another app that generates MIDI streams, so Mozaic will be used to intercept those streams and additional streams from live instruments, merging them with some logic and curation into one stream.

    I don't think anything I'm doing with Mozaic is particularly mindblowing, I'm just not that experience with it.

    Pretty much want to take in one stream representing a live instrument and another that sends blocks of pitches. Each block persists until replaced by a new one. I'll use these inputs to output a stream of MIDI note events from the live stream which are scale-corrected to notes present in the blocks of notes. Main difficulty apart from not remembering much Mozaic is that I'm writing it for a few different input methods that have different formats/playing limitations, so lots of nuance and playability to think about.

  • @OscarSouth said:

    @wim said:
    I'm up for it. I'm not always the best coder, not being trained as a programmer, but I have a fairly good grasp of Mozaic.

    Do you know if there's anything like a discord for mozaic? The help chat is pretty good actually. Want to get better at it so basically looking for places/people to engage in discourse with in the process of improving.

    I don’t know of a Discord. There is a large library of Mozaic scripts on patchstorage.com between that and the help line thread here, you should be able to get on track.

  • @espiegel123 said:

    @OscarSouth said:

    @wim said:
    I'm up for it. I'm not always the best coder, not being trained as a programmer, but I have a fairly good grasp of Mozaic.

    Do you know if there's anything like a discord for mozaic? The help chat is pretty good actually. Want to get better at it so basically looking for places/people to engage in discourse with in the process of improving.

    I don’t know of a Discord. There is a large library of Mozaic scripts on patchstorage.com between that and the help line thread here, you should be able to get on track.

    I've had a look through them and isolated a few useful ones. They'll become more useful as I get more into the flow of reading the syntax/logic. You know if it's possible to read those scripts any easier way than downloading to my ipad and loading into Mozaic?

  • @OscarSouth said:

    @wim said:
    I'm up for it. I'm not always the best coder, not being trained as a programmer, but I have a fairly good grasp of Mozaic.

    Do you know if there's anything like a discord for mozaic? The help chat is pretty good actually. Want to get better at it so basically looking for places/people to engage in discourse with in the process of improving.

    I don't think there is, but it's a big world out there.

  • edited December 2024

    @wim said:

    @OscarSouth said:

    @wim said:
    I'm up for it. I'm not always the best coder, not being trained as a programmer, but I have a fairly good grasp of Mozaic.

    Do you know if there's anything like a discord for mozaic? The help chat is pretty good actually. Want to get better at it so basically looking for places/people to engage in discourse with in the process of improving.

    I don't think there is, but it's a big world out there.

    Cheers, yea just scoping out the environment. Happy to jump on a some kind of IM if anyone wants to talk Mozaic in a more ad hoc manner (anything I put in a forum thread gets thought through a bit more) -- I'm going to be pretty limited for the time being while I chip away at understanding on the thing I'm working on right now.

  • wimwim
    edited December 2024

    Finding patches is a lot easier using the Available Mozaic Scripts wiki page than on PatchStorage. Loading them into Mozaic is the easiest way IMO. The files can be directly opened in a text editor, but contain all the variable states in binary, so editing is just as much trouble.

    My typical MO is to use Textastic on my MacBook for editing. I enable Handoff for a shared clipboard. Then it's just copy and paste between the iPad and MacBook as needed. It's very quick as long as I don't paste when I'm supposed to be copying. 😂
    The amazing @_ki has provided a syntax extension for Textastic that is hugely helpful. https://patchstorage.com/mozaic-language-support-for-code-text-editors/

    For your dynamic note quantizing, https://patchstorage.com/chord-scale-quantize/ might have some code that's applicable.

  • Thanks, I'll have a look at that script in particular.

    I have sublime text set up with the Mozaic syntax, and another app called AirDroid which makes it really easy to put stuff onto the iOS clipboard. That's alright for editing.

    That explains why LLMs haven't got a clue about Mozaic then -- there's no large publicly available corpus of code for webcrawlers to find.

  • Scale correcting notes is very easy in Mozaic.
    There are some built in functions to do this, like 'ScaleQuantize'.
    In the example below, when it receives a Midi note through the @OnMIDINote event, it quantizes it, then sends it out.

        ScaleQuantize
        <result> = ScaleQuantize <midinote>
        Description: takes the note <midinote>, checks if it matches the currently active musical scale (set
        with PresetScale or CustomScale), respecting the current root note (set with
        SetRootNote). If there is a match, the same note is returned. If it doesn’t match the
        active scale, it returns the next higher note which does.
        Parameters and output:
        <result> A midi note number (range 0-127) which matches the active scale
        <midinote> A midi note number (range 0-127)
    
        Example:
    
        @OnMIDINote
         nn = ScaleQuantize MIDINote // @Endgegner tize the incoming midi note
         SendMIDIOut MIDIByte1, nn, MIDIByte3 // send the MIDI note out
        @End
    

    So perhaps you'd set your own CustomScale then just use ScaleQuantize.

    You can do what you want in the @OnMIDI... events of course, so if you have a particular method of altering the note, perhaps with a randomiser or similar then you can. It's also possible to use timers to generate notes without a corresponding @OnMIDIInput event trigger.

  • When dynamically quantizing notes, one thing to watch carefully for is being sure to quantize the note-off properly. If the scale changes while a note that has been quantized is held and then the note filter changes you have to be sure you don't quantize the note-off to the new filter.

  • Thanks all -- this is all really useful and I'll keep it all in mind.

    Right now (due to not being very fluent with Mozaic at present) I've broken out 4 bits of key functionality that I'll need, and I'm creating them all in isolation. I'll later combine them once I have their internal logic working. I've got #1 and #2 sorted but the midi note off part of #3 has obvious logic issues and my brain is too tired to debug it now, so I'll have to wait for tomorrow.

    Here's what I've done so far:

    // app #1
    // convert MIDI CC1 CH1 to notes on CH1
    
    // app #2
    // build pitch mapping from predefined global
    
    // app #3
    // build predefined global from input MIDI notes
    
    // app #4
    // convert MIDI notes to closet pitch in pitch mapping
    
    // app #1
    // convert MIDI CC1 to notes on designated channel
    
    @OnLoad
    
        channel = (14-1)
        ccState = -1 // Initialize to an invalid value to trigger the first note
        currentNote = -1 // Keep track of the currently playing note
    
    @End
    
    @OnMidiCC
    
        if (MIDIChannel = channel) and (MIDIByte2 = 1) and (MIDIByte3 < 128) // Check for CC1 on selected channel
    
            newNote = MIDIByte3 // Use the CC value as the note number
    
            if newNote <> ccState // Only send a new note if the CC value has changed
                if currentNote <> -1 // If a note is currently playing, turn it off
                    sendMidiNoteOff channel, currentNote, 0 // Note Off (Channel 1, Velocity 0)
                    log {Note Off: }, currentNote
                endif
    
                sendMidiNoteOn channel, newNote, 127 // Note On (Channel 1, Velocity 127)
                log {Note On: }, newNote
    
                currentNote = newNote // Update current note
                ccState = newNote // Update ccState
    
            endif
    
        endif
    
    @End
    
    // app #2
    // build pitch mapping from predefined global
    
    @OnLoad
    
        triad = [7, 0, 15]
    
        // get the lowest version of each pitch class in the triad
        for i = 0 to 2
            triad[i] = triad[i] % 12
        endfor
    
        // order triad from low to high (Bubble Sort)
        swapped = true
        while swapped
            swapped = false
            for i = 0 to 1
                if triad[i] > triad[i+1]
                    temp = triad[i]
                    triad[i] = triad[i+1]
                    triad[i+1] = temp
                    swapped = true
                endif
            endfor
        endwhile
    
        pitchMap = [] // Initialize pitchMap
    
        // first 3 values of pitchmap are the same as the triad
        pitchMap[0] = triad[0]
        pitchMap[1] = triad[1]
        pitchMap[2] = triad[2]
    
        // Dynamically populate the rest of the pitchMap
        mapIndex = 3
        for octave = 1 to 9 // Iterate through 9 octaves
            for triadIndex = 0 to 2 // Iterate through the triad
                pitchMap[mapIndex] = triad[triadIndex] + (octave * 12)
                mapIndex = mapIndex + 1
            endfor
        endfor
    
        for i = 0 to 29
            log pitchMap[i]
        endfor
    
    @End
    
    // app #3
    // build predefined global array of len 3 from input MIDI notes on designated channel
    
    
    @OnLoad
    
        channel = 14-1 // Channel 1 (0-indexed)
        FillArray heldNotes, -1, 3 // Array to store held notes
        heldNotesLen = 0 // Track how many notes are held
        log {[}, heldNotes[0], {, }, heldNotes[1], {, }, heldNotes[2], {, }, {]}
        log heldNotesLen
    
    @End
    
    
    @OnMidiNoteOn
    
        pitchClass = MIDIByte2 % 12
    
        if MIDIChannel = channel
    
            // Check if the note is already held
            passOn = false
    
            if heldNotesLen = 0
                heldNotes[0] = pitchClass
                heldNotesLen = 1
                passOn = true
            elseif heldNotesLen >= 3
                passOn = true
            elseif heldNotesLen < 3
                for i = 0 to heldNotesLen
                    if heldNotes[i] = pitchClass
                        passOn = true
                    endif
                endfor
            endif
    
            if not passOn
                heldNotes[heldNotesLen] = pitchClass
                heldNotesLen = heldNotesLen + 1
            endif
    
        endif
    
        log {[}, heldNotes[0], {, }, heldNotes[1], {, }, heldNotes[2], {, }, {]}
        log heldNotesLen
    
    @End
    
    
    @OnMidiNoteOff
    
        pitchClass = MIDIByte2 % 12
    
        if MIDIChannel = channel
    
            passOff = false
            for i = 0 to heldNotesLen
                // log {OFF heldNotesLen: }, heldNotesLen
                // log {OFF heldNotes: }, {[}, heldNotes[0], {, }, heldNotes[1], {, }, heldNotes[2], {]}
                if heldNotes[i] = pitchClass
    
                    for j = i to heldNotesLen // Shift remaining elements to the left to fill the gap
    
                        if not passOff
                            heldNotes[j] = heldNotes[j] + 1
                            passOff = true
                            heldNotes[heldNotesLen] = -1
                            heldNotesLen = heldNotesLen - 1
    
                        endif
    
                    endfor
    
                endif
                // log {OFF POST heldNotesLen: }, heldNotesLen
                // log {OFF POST heldNotes: }, {[}, heldNotes[0], {, }, heldNotes[1], {, }, heldNotes[2], {]}
            endfor
        endif
    
        log {[}, heldNotes[0], {, }, heldNotes[1], {, }, heldNotes[2], {, }, {]}
        log heldNotesLen
    
    @End
    

    I'm sure alongside the non-functional logic in #3 there I'm also doing things in wrong, unoptimised or unintuitive ways. Trying to approach the problem as literally as possible on the first pass and hopefully I'll reveal some ways I can do things better as I learn Mozaic better.

  • @OscarSouth : fwiw midibyte3 will always be less than 128. There isn’t a need to test for that in app1

  • wimwim
    edited December 2024

    Without having given more than a cursory glance at the Note-Off code, I'll mention my usual approach.

    The NoteStates array has one slot for every channel and note combination. So, when a note-on arrives, I save the note number that it was quantized to in there. (If not quantized then I just store the original note in number.)

    Then whenever a note-off arrives, rather than re-quantize it, which by now could be wrong, I look up what the note-on was quantized to instead.

  • edited December 2024

    Edit: Wim answered at the same time as me, but the function names that access the NoteStates array are below :-)

    I didn't read through the logic in detail, but it looks like you are storing the note states. This is useful as Wim says so you don't end up with stuck notes, and Mozaic may have something to help there too so you can avoid your own arrays. There are SetNoteState and GetNoteState commands that provide access to an internal 2d array (i.e. a value for every note) specifically for this kind of purpose... Also, to add to what espiegel123 is saying, @OnMIDICC is an event that only receives MidiCC messages, its pre-filtered. @OnMIDIInput is the 'parent' event that receives everything, so you don't need to worry about values being out of range or a sysex coming through the @OnMIDICC event.

  • edited December 2024

    Cheers for the feedback all, really useful and valuable stuff. Picking this up again today with a fresh mind :)

  • @belldu said:
    Scale correcting notes is very easy in Mozaic.
    There are some built in functions to do this, like 'ScaleQuantize'.
    In the example below, when it receives a Midi note through the @OnMIDINote event, it quantizes it, then sends it out.

        ScaleQuantize
        <result> = ScaleQuantize <midinote>
        Description: takes the note <midinote>, checks if it matches the currently active musical scale (set
        with PresetScale or CustomScale), respecting the current root note (set with
        SetRootNote). If there is a match, the same note is returned. If it doesn’t match the
        active scale, it returns the next higher note which does.
        Parameters and output:
        <result> A midi note number (range 0-127) which matches the active scale
        <midinote> A midi note number (range 0-127)
    
        Example:
    
        @OnMIDINote
         nn = ScaleQuantize MIDINote // @Endgegner tize the incoming midi note
         SendMIDIOut MIDIByte1, nn, MIDIByte3 // send the MIDI note out
        @End
    

    So perhaps you'd set your own CustomScale then just use ScaleQuantize.

    You can do what you want in the @OnMIDI... events of course, so if you have a particular method of altering the note, perhaps with a randomiser or similar then you can. It's also possible to use timers to generate notes without a corresponding @OnMIDIInput event trigger.

    This is really great, thanks -- might not have noticed it! It's not the exact logic I planned (correct to closest note, prioritising higher) but it's close enough to work with less implementation and I can revisit that logic manually later.

  • Think I got the code I was working on above sorted:

    // app #3
    // build predefined global array of len 3 from input MIDI notes on designated channel
    
    
    @OnLoad
    
        channel = 14-1 // Channel 1 (0-indexed)
    
        FillArray heldNotes, -1, 3 // Array to store held notes
        heldNotesLen = 0 // Track how many notes are held
        pass = false
    
        FillArray tonalState, -1, 3
    
        keepNote = -1
        keepNote1 = -1
        keepNote2 = -1
    
        log heldNotesLen, { -- }, {[}, heldNotes[0], {, }, heldNotes[1], {, }, heldNotes[2], {]}
        log { } 
    
    @End
    
    
    @OnMidiNoteOn
    
        pitchVal = MIDIByte2
    
        if MIDIChannel = channel
    
            pass = false // Check if the note is already held
    
            if heldNotesLen = 0
    
                heldNotes[0] = pitchVal
                heldNotesLen = 1
                pass = true
    
            elseif heldNotesLen >= 3
    
                pass = true
    
            elseif heldNotesLen < 3
    
                for i = 0 to heldNotesLen
    
                    if heldNotes[i] = pitchVal
    
                        pass = true
    
                    endif
    
                endfor
    
            endif
    
            pitchClass = pitchVal % 12 
    
            if (not pass) and (pitchClass <> (heldNotes[0] % 12)) and (pitchClass <> (heldNotes[1] % 12))
    
                heldNotes[heldNotesLen] = pitchVal
                heldNotesLen = heldNotesLen + 1
    
            endif
    
        endif
    
        log {|O| }, heldNotesLen, { -- }, {[}, heldNotes[0], {, }, heldNotes[1], {, }, heldNotes[2], {]}
    
    @End
    
    
    @OnMidiNoteOff
    
        pitchVal = MIDIByte2
    
        if MIDIChannel = channel
    
            for i = 0 to heldNotesLen
    
                if heldNotes[i] = pitchVal
    
                    if heldNotesLen <= 1
    
                        heldNotes[0] = -1
                        heldNotes[1] = -1
                        heldNotes[2] = -1
                        heldNotesLen = 0
    
                    elseif heldNotesLen = 2
    
                        for i2 = 0 to 2
    
                            if (heldNotes[i2] <> pitchVal) and (heldNotes[i2] <> -1)
    
                                keepNote = heldNotes[i2]
    
                            endif
    
                        endfor
    
                        heldNotes[0] = keepNote
                        heldNotes[1] = -1
                        heldNotes[2] = -1
                        heldNotesLen = 1
    
                    else
    
                        for i3 = 0 to 3
    
                            if (heldNotes[i3] <> pitchVal) and (heldNotes[i3] <> -1)
    
                                    keepNote1 = heldNotes[i3]
    
                            endif
    
                        endfor
    
                        for i3 = 0 to 3
    
                            if (heldNotes[i3] <> pitchVal) and (heldNotes[i3] <> -1) and (heldNotes[i3] <> keepNote1)
    
                                    keepNote2 = heldNotes[i3]
    
                            endif
    
                        endfor
    
                        heldNotes[0] = keepNote1
                        heldNotes[1] = keepNote2
                        heldNotes[2] = -1
                        heldNotesLen = 2
    
                    endif
    
                endif
    
            endfor
    
        endif
    
        log {|X| }, heldNotesLen, { -- }, {[}, heldNotes[0], {, }, heldNotes[1], {, }, heldNotes[2], {]}
    
    @End
    

    I think I have enough knowledge accumulated now to have a go at building the full app --

    Here goes!

  • @OscarSouth said:
    This is really great, thanks -- might not have noticed it! It's not the exact logic I planned (correct to closest note, prioritising higher) but it's close enough to work with less implementation and I can revisit that logic manually later.

    Simple Scaler has a routine for closest / prioritizing higher amongst it's other quantizing options in you want to compare approaches.

  • edited December 2024

    OK here's the monophonic version -- seems to be working.

    This specific version is intended for use with a Theramini -- the Theramini will scale lock itself to whatever chord is being played on the polyphonic note input on the same channel. It's intended for the Theramini to use a drone sound with some glide, ideally. Alternatively, you can play chords on a keyboard with one hand and play a melody that locks to the chords using the mod wheel.

    Please let me know if there are any obvious mistakes, oversights or bad practices here :)

    // convert MIDI CC 20 to notes locking to triads held on same channel
    
    @OnLoad
    
        channel = (1-1)
        ccState = -1 // Initialize to an invalid value to trigger the first note
        currentNote = -1 // Keep track of the currently playing note
    
        FillArray heldNotes, -1, 3 // Currently held notes
        FillArray triad, -1, 3 // Triad of pitchclasses
        FillArray tonalState, -1, 3 // Current tonal state memory
        FillArray pitchMap, false, 11
    
        CustomScale false, false, false, false, false, false, false, false, false, false, false, false
    
        heldNotesLen = 0 // Track how many notes are held
        pass = false
    
        FillArray tonalState, -1, 2
    
        keepNote = -1
        keepNote1 = -1
        keepNote2 = -1
    
    @End
    
    
    @OnMidiNoteOn
    
        pitchVal = MIDIByte2
    
        if MIDIChannel = channel
    
            pass = false // Check if the note is already held
    
            if heldNotesLen = 0
    
                heldNotes[0] = pitchVal
                heldNotesLen = 1
                pass = true
    
            elseif heldNotesLen >= 3
    
                pass = true
    
            elseif heldNotesLen < 3
    
                for i = 0 to heldNotesLen
    
                    if heldNotes[i] = pitchVal
    
                        pass = true
    
                    endif
    
                endfor
    
            endif
    
            pitchClass = pitchVal % 12 
    
            if (not pass) and (pitchClass <> (heldNotes[0] % 12)) and (pitchClass <> (heldNotes[1] % 12))
    
                heldNotes[heldNotesLen] = pitchVal
                heldNotesLen = heldNotesLen + 1
    
            endif
    
        endif
    
        // log {|O| }, heldNotesLen, { -- }, {[}, heldNotes[0], {, }, heldNotes[1], {, }, heldNotes[2], {]}
    
        if (heldNotesLen = 3)
    
            triad[0] = heldNotes[0] % 12
            triad[1] = heldNotes[1] % 12
            triad[2] = heldNotes[2] % 12
    
            // order triad from low to high
            swapped = true
    
            while swapped
    
                swapped = false
                for i = 0 to 1
    
                    if triad[i] > triad[i+1]
    
                        temp = triad[i]
                        triad[i] = triad[i+1]
                        triad[i+1] = temp
                        swapped = true
    
                    endif
    
                endfor
    
            endwhile
    
            if (triad[0] <> tonalState[0]) or (triad[1] <> tonalState[1]) or (triad[2] <> tonalState[2])
    
                tonalState[0] = triad[0]
                tonalState[1] = triad[1]
                tonalState[2] = triad[2]
    
                for pitchClass = 0 to 11 // Iterate through 12 pitches
    
                    if (tonalState[0] = pitchClass) or (tonalState[1] = pitchClass) or (tonalState[2] = pitchClass)
    
                        pitchMap[pitchClass] = true
    
                    else
    
                        pitchMap[pitchClass] = false
    
                    endif
    
                endfor
    
                CustomScale pitchMap[0], pitchMap[1], pitchMap[2], pitchMap[3], pitchMap[4], pitchMap[5], pitchMap[6], pitchMap[7], pitchMap[8], pitchMap[9], pitchMap[10], pitchMap[11]
    
                log {|NEW TONAL STATE| }, {[}, tonalState[0], {, }, tonalState[1], {, }, tonalState[2], {]}
    
                newNote = ScaleQuantize currentNote // Update current note
    
                if newNote <> ccState // Only send a new note if the CC value has changed
    
                    if currentNote <> -1 // If a note is currently playing, turn it off
    
                        sendMidiNoteOff channel, currentNote, 0 // Note Off (Channel 1, Velocity 0)
                        // log {Note Off: }, currentNote
    
                    endif
    
                    sendMidiNoteOn channel, newNote, 127 // Note On (Channel 1, Velocity 127)
                    // log {Note On: }, newNote
    
                    currentNote = newNote // Update current note
                    ccState = newNote // Update ccState
    
                endif
    
            endif
    
        endif
    
    @End
    
    
    @OnMidiNoteOff
    
        pitchVal = MIDIByte2
    
        if MIDIChannel = channel
    
            for i = 0 to heldNotesLen
    
                if heldNotes[i] = pitchVal
    
                    if heldNotesLen <= 1
    
                        heldNotes[0] = -1
                        heldNotes[1] = -1
                        heldNotes[2] = -1
                        heldNotesLen = 0
    
                    elseif heldNotesLen = 2
    
                        for i2 = 0 to 2
    
                            if (heldNotes[i2] <> pitchVal) and (heldNotes[i2] <> -1)
    
                                keepNote = heldNotes[i2]
    
                            endif
    
                        endfor
    
                        heldNotes[0] = keepNote
                        heldNotes[1] = -1
                        heldNotes[2] = -1
                        heldNotesLen = 1
    
                    else
    
                        for i3 = 0 to 2
    
                            if (heldNotes[i3] <> pitchVal) and (heldNotes[i3] <> -1)
    
                                    keepNote1 = heldNotes[i3]
    
                            endif
    
                        endfor
    
                        for i3 = 0 to 2
    
                            if (heldNotes[i3] <> pitchVal) and (heldNotes[i3] <> -1) and (heldNotes[i3] <> keepNote1)
    
                                    keepNote2 = heldNotes[i3]
    
                            endif
    
                        endfor
    
                        heldNotes[0] = keepNote1
                        heldNotes[1] = keepNote2
                        heldNotes[2] = -1
                        heldNotesLen = 2
    
                    endif
    
                endif
    
            endfor
    
        endif
    
        // log {|X| }, heldNotesLen, { -- }, {[}, heldNotes[0], {, }, heldNotes[1], {, }, heldNotes[2], {]}
    
    @End
    
    
    
    @OnMidiCC
    
        if (MIDIChannel = channel) and (MIDIByte2 = 20) // Check for CC 20 on selected channel
    
            newNote = ScaleQuantize MIDIByte3 // Use the CC value as the note number
    
            if newNote <> ccState // Only send a new note if the CC value has changed
    
                if currentNote <> -1 // If a note is currently playing, turn it off
    
                    sendMidiNoteOff channel, currentNote, 0 // Note Off (Channel 1, Velocity 0)
                    // log {Note Off: }, currentNote
    
                endif
    
                sendMidiNoteOn channel, newNote, 127 // Note On (Channel 1, Velocity 127)
                // log {Note On: }, newNote
    
                currentNote = newNote // Update current note
                ccState = newNote // Update ccState
    
            endif
    
        endif
    
    @End
    
  • Logically it looks ok. Stylistically I think your up to the window dressing stage. I always wrap log messages in an 'if _logEvents = true' statement and then declare and set the _logEvents variable in the @onload event. It's easier than commenting and uncommenting log code. Other than that, I'd recommend putting in some code for @onhoststop, perhaps a midi panic of sorts. Quite a few folks use the @onshiftdown ui event to stop any notes as well if things go awry.

Sign In or Register to comment.