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.

MOZAIC - Create your own AU MIDI plugins - OUT NOW!

14950525455106

Comments

  • @McD said:
    Sample test drive of my Mozaic script in it's current form. I can take a single MIDI note and convert it to:

    1: large chordal voicing - suitable for orchestral ensembles
    2: apply a configurable delay and arpeggiate the chord voices - suitable for pianos, harps

    I started up 2 copies and fed one to some strings and a synth and the 2nd to a piano and harp:

    What a brilliant script @McD looking forward to giving this a spin.

  • @McD said:
    Sample test drive of my Mozaic script in it's current form. I can take a single MIDI note and convert it to:

    1: large chordal voicing - suitable for orchestral ensembles
    2: apply a configurable delay and arpeggiate the chord voices - suitable for pianos, harps

    I started up 2 copies and fed one to some strings and a synth and the 2nd to a piano and harp:

    hot damn, I really love this!

  • edited November 2019

    This morning we had a discussion about killing hanging notes. I want to state that I am against the killing of notes. When they are hanging it is because off a fault of the controller, the notes are not to blame.
    Since some hosts do not like to get their midi buffer filled with thousands of delayed midi commands I created this script to release all the notes that I accidentally hung, without any killing.
    While programming with Mozaic this is a handy tool to keep in your midi effects chain.

    https://patchstorage.com/abolisher/

  • @Alfred catch and release? It does seem more humane, though recent studies indicate that hung notes do feel pain.

  • @mojozart Yes I feel their pain. Allways franticly searching when I hear one being hung...
    I thought this better and more humane then building a mouse trap.

  • @Alfred said:
    This morning we had a discussion about killing hanging notes. I want to state that I am against the killing of notes. When they are hanging it is because off a fault of the controller, the notes are not to blame.
    Since some hosts do not like to get their midi buffer filled with thousands of delayed midi commands I created this script to release all the notes that I accidentally hung, without any killing.
    While programming with Mozaic this is a handy tool to keep in your midi effects chain.

    https://patchstorage.com/abolisher/

    Nice work. It's fun and instructive to read the script too. I just noticed you have a whole series of MIDI Scripts with Branding in the naming.

  • @McD I just published a note state tracking code example script showing how to implement note state tracking to enable functions acting on active notes only - the demo code 'mutes' all active notes.

    You could use method or even parts of the sample code in your chorder script to send NoteOffs when a knob change for chord layout would result in hanging notes.

    .

    In my processing scripts (like Midi Filter and Transpose or CopyCat) i use a different strategy - i use the NoteStateArray to store the generated output note (using a combined single value v= ch*256 + note) and channel / input note in the OnMidiNoteOn. And when OnMidiNoteOff is triggered, i lookup the output and output a NoteOff using the stored note and channel. This will always turn off the correct note, regardless of what processing setting were changed between NoteOn and NoteOff.

  • @_ki said:
    @McD I just published a note state tracking code example script showing how to implement note state tracking to enable functions acting on active notes only - the demo code 'mutes' all active notes.

    You could use method or even parts of the sample code in your chorder script to send NoteOffs when a knob change for chord layout would result in hanging notes.

    .

    In my processing scripts (like Midi Filter and Transpose or CopyCat) i use a different strategy - i use the NoteStateArray to store the generated output note (using a combined single value v= ch*256 + note) and channel / input note in the OnMidiNoteOn. And when OnMidiNoteOff is triggered, i lookup the output and output a NoteOff using the stored note and channel. This will always turn off the correct note, regardless of what processing setting were changed between NoteOn and NoteOff.

    Nice! I was thinking about doing the channel multiplier for the same purpose. One could pack other things in there with other multipliers as well.

  • _ki_ki
    edited November 2019

    @wim In the CopyCat script i pack three values into a single NoteStateArray entry - there is even documentatiom about this technique in the scripts source :

    // The NoteState array is used to store three values in one
    // place. The additional data is stored in the higher 'bits'
    //   mask      = 256
    //   mask2     = mask*mask
    //
    //   storedVal = valC * mask2 + valB * mask + valA
    //
    // The access to the stored data is done with
    //   valC = RoundDown storedVal/mask2
    //   valB = (RoundDown storedVal/mask) % mask
    //   valA = storedVal % mask
    //
    // The stored values and the EMPTY constant to flag unused entries
    // need to be positive and between 0 and mask. 
    // 
    // In case of CopyCat:
    // - lower  valA stores EMPTY or note output for input note 
    //   This allows to check for incomming double notes
    //
    // - middle valB stores note delay for input note
    //
    // - upper  valA stores EMPTY or input-note for output-note
    //   This allow to check for outgoing double notes.
    //
    // - valA and valB are set/reset at the same time, so they
    //   sometimes are accessed with  
    //      valAB = RoundDown storedVal/mask
    
  • wimwim
    edited November 2019

    Thanks @_ki! Saved me some brain cells once again. B)

  • @_ki said:
    In the CopyCat script, I pack three values into a single NoteStateArray entry

    Do you think this encoding could be extended to hold up 8 values (64-bit integers)?

    mask8 = maskmaskmaskmaskmaskmaskmask*mask

    No hurry. I can write some tests and figure it where the limit is.

  • _ki_ki
    edited November 2019

    @McD , @wim Two additional things i forgot to mention and just added to my above code sample:

    The stored values need to be positive and between 0 and mask. And the EMPTY constant also needs to be in that range.
    Otherwise the proposed simple packing operation storedVal = valC * mask2 + valB * mask + valA will not work.

    .

    Regarding packing 8 values:

    I know that Mozaic internally uses only a single datatype for the values - a floating point value. The largest integer that can be stored therefore depends on the number of mantissa bits used. In a 32 bit float, there are usually 23 bits for the mantissa. And in a 64 bit float there are 52 mantissa bits.

    So you probably can't store integer values with 64 bits as the internal number format needs some bits for the exponent and sign bits (see wikipedia )

  • @_ki said:
    @McD , @wim Two additional things i forgot to mention and just added to my above code sample:

    The stored values need to be positive and between 0 and mask. And the EMPTY constant also needs to be in that range.
    Otherwise the proposed simple packing operation storedVal = valC * mask2 + valB * mask + valA will not work.

    .

    Regarding packing 8 values:

    I know that Mozaic internally uses only a single datatype for the values - a floating point value. The largest integer that can be stored therefore depends on the number of mantissa bits used. In a 32 bit float, there are usually 23 bits for the mantissa. And in a 64 bit float there are 52 mantissa bits.

    It’s a bit more complicated even. For integer-variables it uses 32 bits integers internally. But it can lose that precision when:
    A. You change its variable type (e.g. when using it in a floating point context)
    B. When a variable is loaded from a saved state

    So you could in practice use 32 bits of packed data (and even perform bitwise operations on them), but it’s not guaranteed to work under every circumstance.

  • @brambos Thanks for the clarification, i'll keep that in mind when i use packed values.

  • Hey I just wrote two scripts for use in Loopy which accomplish the same thing in different ways and am wondering which would be considered ‘better’ or more efficient, and of course if there’s more efficient ways to write either of these scripts, or something different altogether for the idea. Both work as I’m intending so I’m wondering what other more experienced programmers would do in this case, and also figure it could be a good example for other beginners.

    The intention is to ‘remember’ a set number of pads/notes played and then recall those with the push of a single pad/note. This again is for live use in Loopy to mute/unmute any number of tracks with the push of a single button, currently not supported as a midi binding option in Loopy, though fairly sure it will be with the next Loopy edition...but I can’t wait... 😉

    I actually ran this by the forum back in June or July to see how I was doing when just starting to try things out and actually @wim you said you were too tired at the moment to look at it but would check it out later (no problem at all of course, I got too busy with other things to continue with Mozaic anyways until now)...That first script used NoteStates to remember the notes played, but was quite messy and now while in the process of reviewing it before posting again I came up with another idea for how to do it with arrays instead of NoteState saving (also wanted to try to get a handle on arrays and a use for it in the first place), which I thought would be more efficient. But then realized NoteState saving was totally superfluous in the first script and could just be done with variables, BUT that pointed me to using note states instead of arrays in the SECOND script to prevent a possible accidental double memorizing and sending of the same notes within an array... Learning is fun. 😛

    ANYWAYS, so now I have two which I’m unsure about which is better! Here’s the first using simple variables:

    @onload
    log {ChoozyGroups 1.2}
    //Loopy tracks
      L1 = 0
      L2 = 0
      L3 = 0
      L4 = 0
      L5 = 0
      L6 = 0
      L7 = 0
      L8 = 0
      L9 = 0
      L10 = 0
      L11 = 0
      L12 = 0
      sendtrigger = 64
      reset = 65 //erases 'memory'
    @end
    
    @OnMidiNote
      if MIDIByte1 = 144 and MIDIByte2 = reset
      L1 = 0
      L2 = 0
      L3 = 0
      L4 = 0
      L5 = 0
      L6 = 0
      L7 = 0
      L8 = 0
      L9 = 0
      L10 = 0
      L11 = 0
      L12 = 0
      log {memory reset}
      endif
    
    
    //store which tracks were either muted or selected in Loopy
    //the first midibyte2 is for the set of pads designated to 
    //muting, the second is for the set of pads for selecting
    
        if MIDIChannel = 0 and (MIDIByte2 = 0x24 or MIDIByte2 = 0x20)
         L3 = 0x24
    
        elseif MIDIChannel = 0 and (MIDIByte2 = 0x25 or MIDIByte2 =0x21)
         L6 = 0x25
    
        elseif MIDIChannel = 0 and (midibyte2 = 0x26 or MIDIByte2 =0x22)
         L9 = 0x26
    
        elseif MIDIChannel = 0 and (MIDIByte2 = 0x27 or MIDIByte2 =0x23) 
         L12 = 0x27
    
        elseif MIDIChannel = 0 and (MIDIByte2 = 0x1c or MIDIByte2 =0x18)
         L2 = 0x1c
    
        elseif MIDIChannel = 0 and (midibyte2 = 0x1d or MIDIByte2 =0x19)
         L5 = 0x1d
    
        elseif MIDIChannel = 0 and (MIDIByte2 = 0x1e or MIDIByte2 =0x1a) 
         L8 = 0x1e
    
        elseif MIDIChannel = 0 and (MIDIByte2 = 0x1f or MIDIByte2 =0x1b) 
         L11 = 0x1f
    
        elseif MIDIChannel = 0 and (MIDIByte2 = 0x14 or MIDIByte2 =0x10)
         L1 = 0x14
    
        elseif MIDIChannel = 0 and (midibyte2 = 0x15 or MIDIByte2 =0x11)
         L4 = 0x15
    
        elseif MIDIChannel = 0 and (MIDIByte2 = 0x16 or MIDIByte2 =0x12) 
         L7 = 0x16
    
        elseif MIDIChannel = 0 and (MIDIByte2 = 0x17 or MIDIByte2 =0x13) 
         L10 = 0x17
    
        endif
    
    
    //recall and send recorded tracks  
    
        if MIDIByte1 = 144 and MIDIByte2 = sendtrigger
            SendMIDINoteOn 0, L3, 127
            SendMIDINoteOn 0, L6, 127
            SendMIDINoteOn 0, L9, 127
            SendMiDINoteOn 0, L12, 127
            SendMIDINoteOn 0, L2, 127
            SendMIDINoteOn 0, L5, 127
            SendMIDINoteOn 0, L8, 127
            SendMiDINoteOn 0, L11, 127
            SendMIDINoteOn 0, L1, 127
            SendMIDINoteOn 0, L4, 127
            SendMIDINoteOn 0, L7, 127
            SendMiDINoteOn 0, L10, 127
            SendMIDINoteOff 0, L3, 0, 100.0
            SendMIDINoteOff 0, L6, 0, 100.0
            SendMIDINoteOff 0, L9, 0, 100.0
            SendMIDINoteOff 0, L12, 0, 100.0
            SendMIDINoteOff 0, L2, 0, 100.0
            SendMIDINoteOff 0, L5, 0, 100.0
            SendMIDINoteOff 0, L8, 0, 100.0
            SendMIDINoteOff 0, L11, 0, 100.0
            SendMIDINoteOff 0, L1, 0, 100.0
            SendMIDINoteOff 0, L4, 0, 100.0
            SendMIDINoteOff 0, L7, 0, 100.0
            SendMIDINoteOff 0, L10, 0, 100.0
         log {memory recalled}
        endif
      sendmidithru
    @End
    
    @Description
    Memorizes a set number of pads/notes to be recalled with a single pad/note
    -using variables *note: this will always send all 12 variables 
    @End 
    

    AND here’s the second version with NoteStates, feels prettier to me but still wonder if it’s better in the end...

    @OnLoad
    log {CHOOZY GROUPS 2.5}
      rangestart = 20 //start of range of notes to store
      rangeend = 40 //end of range of notes to store
      ResetNoteStates
      counter = rangestart
      recordchan = 0 //channel to listen for and send to
      recordtrigger = 64 //clear memory-start 'memorizing' notes
      sendtrigger = 65 //send 'memorized' notes
    @End
    
    @OnMidiNoteOn
    
      if (MIDIChannel = recordchan) and (MIDINote = recordtrigger)
        ResetNoteStates
        Log {Received trigger note on }, MIDINote, {. Memory reset.}
      endif
    
          //store notes if 'memorize' trigger is armed
     if (MIDIChannel = recordchan) and (MIDINote <> recordtrigger) and (MIDINote <> sendtrigger)
      n = MIDINote
    
      //only listen for Loopy 'tog mute' pads
       if ((n >=20 and n<=23) or (n>=28 and n<=31) or (n>=36 and n<=39))
        SetNoteState 0, n, n 
        log (MIDINote), {-stored}
    
        //adjust Loopy 'selected' pads to store as 'tog mute'
       elseif ((n>=16 and n<=19) or (n>=24 and n<=27) or (n>=32 and n<=35))
        SetNoteState 0, n + 4, (n + 4)
        log (MIDINote + 4), {-stored}
      endif
    
     endif
    
          //send memorized notes
      if MIDINote = sendtrigger
        counter = rangestart
            repeat
          if (GetNoteState 0, counter) > 0
          SendMIDINoteOn 0, (GetNoteState 0, counter), 127
          log (GetNoteState 0, counter), {-sent}
          endif
          inc counter
        until counter = rangeend
      endif
     SendMIDIThru
    @End 
    
    @Description
    Memorizes a set number of pads/notes to be recalled all at once with a single pad/note
    -using NoteStates
    @End 
    

    Any comments/input is super helpful as I’m learning! Thanks_

  • @ManWhoWouldBeStrings The best way to learn this stuff is study as many different scripts as you can find to get a sense of style and learn neat tricks.

    It looks like you are testing to much. Start with testing if it is the right channel. Then inside that block test if it is the right note. Also if you first test if the channel is not your channel, then right away you can sendMidiThru.
    I would make a separate handler for the toggling so you only have to change your code in one place.

  • @ManWhoWouldBeStrings The first version can be shorted to less than half length just by using arrays:

    @OnLoad
      Log {ChoozyGroups 1.2}
      FillArray loopy, 0, 12
      sendtrigger = 64
      reset = 65 //erases 'memory'
    
      //              L1   L2  L3   L4   L5.  L6
      trigger[0] = [0x14,0x1c,0x24,0x15,0x1d,0x25]    
      //              L7   L8  L9   L10  L11  L12
      trigger[6] = [0x16,0x1e,0x26,0x17,0x1f,0x27]    
      // The second trigger is always 4 less than the first 
    @end
    
    @OnMidiNote
      if MIDIByte1 = 144 and MIDIByte2 = reset
        FillArray loopy, 0, 12
        Log {memory reset}
      endif
    
    //store which tracks were either muted or selected in Loopy
    //the first midibyte2 is for the set of pads designated to 
    //muting, the second is for the set of pads for selecting
    
      if MIDICHannel = 0
        for _idx = 0 to 11
          if MIDIByte2 = trigger[_idx] or MIDIByte2=trigger[_idx]-4
            loopy[_idx] = trigger[_idx]
          endif
        endfor
      endif
    
    //recall and send recorded tracks  
    
      if MIDIByte1 = 144 and MIDIByte2 = sendtrigger
        for _idx = 0 to 12
          SendMIDINoteOn 0, loopy[_idx], 127
          SendMIDINoteOff 0,loopy[_idx],0, 100 // delayed note off
        endfor
    
        log {memory recalled}
      endif
      SendMIDIThru
    @End
    
    @Description
    Memorizes a set number of pads/notes to be recalled with a single pad/note
    -using variables *note: this will always send all 12 variables 
    @End 
    

    .

    To enhance your script , you should setup a visually clutter free GUI just showing the name, also
    set the icon name by extending your @OnLoad :

    @OnLoad
      SetShortName {CHOOZY}
      LabelPads { }
      LabelKnobs {ChoozyGroups 1.2}
      LabelXY { }
    
      // Cleanup unused UI elements to visually unclutter
      for _i = 0 to 10
        LabelKnob _i,{ }
        SetKnobValue _i,0
      endfor
      SetXYValues 0,0
    @End
    

    You could also make a pad recall the current set. By moving the recall code into a
    user event which then is called from both the sendtrigger detection and in @OnPadDown.

    @AddinsToExistingCofr
       // Add this line to the end of the above OnLoad
       LabelPad 0,{Recall}
    
      // Change in @OnMidiNote
      if MIDIByte1 = 144 and MIDIByte2 = sendtrigger
        Call @RecallTracks
      endif
    @End
    
    @OnPadDown
      if LastPad = 0
        Call @RecallTracks    
      endif
    @End
    
    @RecallTracks
      for _idx = 0 to 12
        SendMIDINoteOn 0, loopy[_idx], 127
        SendMIDINoteOff 0,loopy[_idx],0, 100 // delayed note off
      endfor
    
      log {memory recalled}
    @End
    
  • _ki_ki
    edited November 2019

    @ManWhoWouldBeStrings I Just looked through your second source - you are basically using only one dimension of the 2D note state array.
    So you can replace all SetNoteState 0,a,b with state[a] = b and all GetNoteState 0,a with state[a] to start using the correct array type.

    .

    My above script ent one step further and did not need to step through a note range to find the right one, but defined the correct value in a trigger[] array - therefore it always only needs to step through these 12 entries.

  • I'm uploaded the "McOrchestrator" Mozaic Script building on the work @_Ki and I did with the "One Finger Orchestra" concepts.

    By default it outputs Arpeggio's on Channel 0 and Voiced Chords on Channel 1. There are knobs for a lot of tweaking parameters.

    I made a quick demo in AUM using:

    Ravenscroft 275 for Arpeggios
    Noise Strings for chords
    AudioKit D1 Digital Synth chords

    FX from:
    Visual Mixer
    Eventide Black Hole
    TB Reverb

    https://patchstorage.com/mcorchestrator/

    Just use "One Finger" at a time to play it (single notes are best). It can generate a lot of notes quickly. The Arps are "Sample and Hold" and the Chords will follow the Note Off's of the input.

    Chords also have a Delay option from fast strum to one note per second or so. They can complete with the Arp output in good and really bad ways.

  • Hi @McD is see where you are hanging the notes in the arp section.
    Give them a release ticket right away. Another noteOn with velocity 0 and delay 200 will do.

  • @Alfred said:
    Hi @McD is see where you are hanging the notes in the arp section.
    Give them a release ticket right away. Another noteOn with velocity 0 and delay 200 will do.

    Thanks. I'll do that. I'm having fun making up new chord/arp combinations/sequences.

  • Next update is getting some noticeable performance upgrades and a system that will try to rescue you from accidental infinite loops. :)

  • @brambos said:
    Next update is getting some noticeable performance upgrades and a system that will try to rescue you from accidental infinite loops. :)

    Is there any example code for making infinite loops?

    I'm glad you're updating the app. I hope we can help find more users of the tool to reward your investment in the product.

  • @McD said:

    @brambos said:
    Next update is getting some noticeable performance upgrades and a system that will try to rescue you from accidental infinite loops. :)

    Is there any example code for making infinite loops?

    // don’t try this at home
    while yes
      A=0
    endwhile
    
  • @brambos said:
    // don’t try this at home
    while yes
    A=0
    endwhile

    I went to the coffee shop and it works! So I put some SendMIDIOut statements in the Loop and the whole room started dancing. When I got home... it stopped working.

  • @McD said:

    @brambos said:
    // don’t try this at home
    while yes
    A=0
    endwhile

    I went to the coffee shop and it works! So I put some SendMIDIOut statements in the Loop and the whole room started dancing. When I got home... it stopped working.

    :D

  • When good boffin humor goes bad.

  • McDMcD
    edited November 2019

    @Alfred said:
    Hi @McD is see where you are hanging the notes in the arp section.
    Give them a release ticket right away. Another noteOn with velocity 0 and delay 200 will do.

    I added the extra SendMIDIOn with 0 velocity and (200 + Delay). Delay is a variable which allows the users to dial-in more note length to taste.

    I probably need to have a delay for chords and another for arp's since they control the "strum" simulation as well in chord play and the ambient slow roll out of notes and a control for Arp "release". I upload the patched script as v0.8.

    I need to give the Chord Maps useful names. Right now they echo the Scales of "The-Chordulator" since @wim's code showed me a solid way to add labeling for knobs and I didn't have ready names for the various chord maps.

    That's where the fun lies for me... generating chord maps. It's composing really. I had a "rest" feature and will probably add it back to
    avoid quantized brain syndrome.

    It don't men a thing if it ain't got that fascination' rhythm.

    Oh yeah. I should dial in "Scha-wing" with another knob. I'm going to end up with 22 knobs with knob 22 being used to load a new knobset or maybe not.

    I've waiting to hear something that someone else makes with this script to see what that feels like.

  • edited November 2019

    Hey thanks @_ki and @Alfred for the suggestions and code for my little attempts at scripts. SUPER helpful for my understanding of things. I understand better now how to use arrays in interesting ways and the difference with the note state array system. Much appreciated.

    However there’s something going on with the script you wrote @_ki which I can’t figure out, which is that it is always sending 0x14 (the note stored in the trigger[0] array) on ‘memory recall’...

    Also,
    @_ki said:

    My above script ent one step further and did not need to step through a note range to find the right one, but defined the correct value in a trigger[] array - therefore it always only needs to step through these 12 entries.

    The reason I did it the way I did before was to only send the notes that had been stored, rather than sending 12 notes every time whether or not they had stored values or simply 0. I tried it again using your array system like this...

    //recall and send recorded tracks  
    
      if MIDIByte1 = 144 and MIDIByte2 = sendtrigger
        counter = 0
        repeat
          if loopy[counter] > 0
            SendMIDINoteOn 0, loopy[counter], 127
            SendMIDINoteOff 0,loopy[counter],0, 100 // delayed note off
          endif
          inc counter
        until counter = 12
    
        log {memory recalled}
      endif
    

    What do you think?

    @_ki said:

    To enhance your script , you should setup a visually clutter free GUI just showing the name,

    I left any GUI stuff out as I don’t actually use the (super cool) GUI system with Mozaic as I keep Loopy open on screen all the time and manage everything else with an external controller (with my setup I find things start to go off the rails if I switch between IAA apps too much live). However I was just thinking...in your opinion would it be a good idea to JUST use one of the Pads like you suggested, midi-bound to a command (the same sendtrigger) in AUM, and so just use the OnPadDown - Call@RecallTracks instead of testing for the note itself? One less thing to test for?

  • I have a few more general questions (rather than concerning the stuff above in my last post) regarding what is a more efficient way to setup a few simple scripts running in AUM for controlling Loopy in interesting ways.

    Is it better to:
    embed each of the fairly simple codes I’ve got going into one longer script
    OR
    load multiple instances of Mozaic with the separate scripts

    And with multiple instances of Mozaic, is it better to:
    Load them all in a chain in one AUM channel
    OR
    Give them each seperate channels

    ?

Sign In or Register to comment.