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!

14243454748103

Comments

  • edited August 2019

    @motmeister thanks for perfect patch ( envelope ) .
    1, Decreasing pad for decreasing cc number doesn’t work.
    2, I think sustain doesn’t work as sustain, it works like attack ( increasing value) .
    And one suggestion, can you add PitchBend to envelope ?

  • edited August 2019

    @Bryan I'm looking at the Shift-shenanigans, but I can't seem to reproduce the issue (currently testing on my iPad Air 2). Is there way to consistently trigger the issue?

    Internally all events are properly triggered/detected 100% of the time and the script also seems to work as expected (as far as I understand what to expect; short press is mode-switch, long-press is function-switch).

    Edit: just thinking.. could it be that there is a rogue setting somewhere in the Saved State? Which could explain why a re-upload solves the issue (this clears the saved states).

  • @brambos
    Is it intended that releasing shift outside of button area after press+hold won't trigger @onshiftup

  • @recccp said:
    @brambos
    Is it intended that releasing shift outside of button area after press+hold won't trigger @onshiftup

    That's standard behavior on iOS, but I've now included the "Release Outside" event to trigger the OnShiftUp as well in the upcoming update.

  • @brambos said:

    @recccp said:
    @brambos
    Is it intended that releasing shift outside of button area after press+hold won't trigger @onshiftup

    That's standard behavior on iOS, but I've now included the "Release Outside" event to trigger the OnShiftUp as well in the upcoming update.

    What I have witnessed is that reuploading doesn't change anything and it was failing for me even when the release was in the shift key area. Adding the log line was the only thing for me that made a difference. If the iPad Air is slower than my iPad gen 6, it could be that the longer execution time prevents the issue from happening.

  • @brambos said:

    @recccp said:
    @brambos
    Is it intended that releasing shift outside of button area after press+hold won't trigger @onshiftup

    That's standard behavior on iOS, but I've now included the "Release Outside" event to trigger the OnShiftUp as well in the upcoming update.

    Great! Thank you 😊

  • edited August 2019

    @pejman said:
    @motmeister thanks for perfect patch ( envelope ) .
    1, Decreasing pad for decreasing cc number doesn’t work.
    2, I think sustain doesn’t work as sustain, it works like attack ( increasing value) .
    And one suggestion, can you add PitchBend to envelope ?

    I’ll check the two items (Decrease and Sustain). Maybe I got the wrong version uploaded. Isn’t PitchBend a choice of CC? I might need a better explanation of what you’re looking for. I’ll be back after I’ve checked the other issues...

    You were right about the Decrease for CC. There was a small coding error.

    When I tried Sustain, it followed my intention. As long as I held the note, the level stayed at the ending level for Decay. When I let go, the envelope entered the Release phase. Btw, if it receives another trigger before Release is finished, it will start the new trigger’s Attack Level where Release was interrupted. This is by design. When starting attacks at zero, it’s choppy.

    I haven’t used Pitch Bend much. I was wrong, it’s not a CC but has it’s own MIDI command. I’ll look into adding that for a future release.

    I’ll replace the version on patchstorage in a bit, but here’s a DropBox link to the corrected version:

    https://www.dropbox.com/s/a7p8orqkii4l58t/Envelope+.mozaic?dl=0

    Let me know if you’re still having problems.

  • edited August 2019

    @brambos
    I’ve noticed that Flow eats A LOT of DSP (20-30%) even when I run it with no notes recorded.
    I’ve optimized the OnTimer, so now with OnMetroPulse disabled I hover around 4% with Timer firing every 5ms. (This is not on GitHub yet)

    Here’s my mystery. If you take a look at my OnMetroPulse,
    https://github.com/peblin/flow-sequencer/blob/fec494d6eaa0fcd63076cad517b93b9421cbd9cc/flow-sequencer.script#L185
    you’ll see it’s LONG, but that all heavy-lifting is in if statements that will evaluate to false for an empty pattern. So the OnMetroPulse is something like:

    For each pattern
      If pattern is active (soloed) pattern
        If current step part has CC recorded, play it
        If current step part has recorded notes, play them
      Endif
    Endfor
    

    When the patterns are empty, running with one SOLOed patterh the above pseudocode results in 6 if-statements, that all evaluate to false.

    This gives ~20-30% DSP on my iPad Pro 2017, which feels very harsh.

    Now, if I enable ”multi-track” playback, OnMetroPulse will run 76 if statements - consuming *the same amount of DSP as for just one track.

    I’ve tried moving the constructs inside IF-statments to their own methods and it seems to reduce CPU load somewhat - making me think that Mozaic’s parsing of the statements is expensive even when they don’t run. Parsing seem to be done once per event execution as I don’t get a penalty running multiple patterns.

    Would be very grateful if you could shed some light on this and how I should ”think” to get my code running as efficiently as possible. I’d like to add more fun features to flow like racheting but right now even running empty patterns with ”trivial” if-statements consume so much DSP that I can’t have two instances of Flow running at the same time without going above 100% sometimes.

  • @Peblin said:
    Would be very grateful if you could shed some light on this and how I should ”think” to get my code running as efficiently as possible. I’d like to add more fun features to flow like racheting but right now even running empty patterns with ”trivial” if-statements consume so much DSP that I can’t have two instances of Flow running at the same time without going above 100% sometimes.

    Longer event calls will cause more processing, there's no way around that. But when a conditional ("if") statement has already evaluated to false it will simply skip over all subsequent lines until it has reached its closing "endif" statement. But we're talking tiny nibbles of CPU, barely noticeable on a reliable CPU meter.

    All lines are interpreted as byte-code (well, int-code, technically) so interpreting them shouldn't add too much processing, since it's just a bunch of integer comparisons. That's how most virtual machines work.

    Looking at your observations, I can only explain the CPU behavior with: measuring CPU load of iOS plugins in hosts is dark magic and doesn't actually tell you what the CPU is doing. When I run Mozaic in my profiler/debugging environment and check the readouts, it's actually predictable and makes sense.

    I wouldn't worry too much about what CPU meters tell you. The common-sense guideline with Mozaic is the same as with all other apps: if it does more things, it will have to work harder. :)

  • _ki_ki
    edited August 2019

    @peblin In my scripts i try reduce the loop count in OnMetroPulse by not looping over all possibilitys and testing to see if they are active. Instead whenever the setup changes, i compile a list of active things to be done and only iterate over these.

    In my current WIP thread this is still too much, as different thing (out of 21) may run each of several of the metropulses. To further reduce the load, i now have an array with entry for each metro pulse that either states that nothing happens or points to a linked list of things to do. All the 'to-do' entries and next pointers are stores in two arrays, one for the what-to-to entry and a second array for next-index).

    @OnMetroPulse
      toDoIDX = taskHeadArray[ CurrentMetroPulse ]
      while toToIDX <> DO_NOTHING
        doThis = chainEntry[ toDoIDX ]
        // Do what doThis refers
        toDoIDX = chainNext[ toDoIDX ]
      endif
    @End
    

    So, using this compiled task list, the onMetro pulse is only active in pulses were taskHeadArray[] is set and then only does the things that are in the chain for that metro pulse.

    The compilation of the three arrays incorporates a taskTailArray that always points to the end of each of the chains to allow for fast appending of new entires.
    The add something, there is an index to the nextUnusedChainEntry referencing into chainEntry[] and chainNext[]. Just fill in the data, update the chain links and increment nextUnusedChainEntry. So basically adding some task to a partly filled taskTailArray is simple and does not need sorting or anything complicated.

    To get that bug-free, it needed a lot of Log() messages for the first tries, but now it works flawless :)

  • @brambos said:

    @Peblin said:
    Would be very grateful if you could shed some light on this and how I should ”think” to get my code running as efficiently as possible. I’d like to add more fun features to flow like racheting but right now even running empty patterns with ”trivial” if-statements consume so much DSP that I can’t have two instances of Flow running at the same time without going above 100% sometimes.

    Longer event calls will cause more processing, there's no way around that. But when a conditional ("if") statement has already evaluated to false it will simply skip over all subsequent lines until it has reached its closing "endif" statement. But we're talking tiny nibbles of CPU, barely noticeable on a reliable CPU meter.

    All lines are interpreted as byte-code (well, int-code, technically) so interpreting them shouldn't add too much processing, since it's just a bunch of integer comparisons. That's how most virtual machines work.

    Looking at your observations, I can only explain the CPU behavior with: measuring CPU load of iOS plugins in hosts is dark magic and doesn't actually tell you what the CPU is doing. When I run Mozaic in my profiler/debugging environment and check the readouts, it's actually predictable and makes sense.

    I wouldn't worry too much about what CPU meters tell you. The common-sense guideline with Mozaic is the same as with all other apps: if it does more things, it will have to work harder. :)

    Thanks @brambos, appreciate the clarification. I also assumed the ”nibbles of CPU”, that’s why I find it weird that my 6 false IF-statements consume so much DSP (on the meter at least). Because those 6 IFs doesn’t really fall under ”does more things, work harder”-guideline - the ”more things” in OnMetroPulse is not executing and thus shouldn’t affect performance.

    And I do know that I shouldn’t worry about CPU/DSP meters, but the crackling that results from running a few Flow instances with ”trivial” patterns tells a different story. :)

    I’ll do some more experiments and see if I’m chasing ghosts or not.

  • @_ki said:
    @peblin In my scripts i try reduce the loop count in OnMetroPulse by not looping over all possibilitys and testing to see if they are active. Instead whenever the setup changes, i compile a list of active things to be done and only iterate over these.

    In my current WIP thread this is still too much, as different thing (out of 21) may run each of several of the metropulses. To further reduce the load, i now have an array with entry for each metro pulse that either states that nothing happens or points to a linked list of things to do. All the 'to-do' entries and next pointers are stores in two arrays, one for the what-to-to entry and a second array for next-index).

    @OnMetroPulse
      toDoIDX = taskHeadArray[ CurrentMetroPulse ]
      while toToIDX <> DO_NOTHING
        doThis = chainEntry[ toDoIDX ]
        // Do what doThis refers
        toDoIDX = chainNext[ toDoIDX ]
      endif
    @End
    

    So, using this compiled task list, the onMetro pulse is only active in pulses were taskHeadArray[] is set and then only does the things that are in the chain for that metro pulse.

    The compilation of the three arrays incorporates a taskTailArray that always points to the end of each of the chains to allow for fast appending of new entires.
    The add something, there is an index to the nextUnusedChainEntry referencing into chainEntry[] and chainNext[]. Just fill in the data, update the chain links and increment nextUnusedChainEntry. So basically adding some task to a partly filled taskTailArray is simple and does not need sorting or anything complicated.

    To get that bug-free, it needed a lot of Log() messages for the first tries, but now it works flawless :)

    This is very impressive @_ki! LOOONG time since I meddled with array implementations of linked lists :smiley:

    But, doesn’t // Do what doThis refers result in a lot of testing/IFs anyway to perform the correct operation?

    A very elegant solution nevertheless!

    In my specific case I don’t think it matters though - I only have two main states to check, ”do I need to play notes” and ”do I need to play CCs” which should be nano/microsecond operations, not bring the DSP to 30%. But I will dig more.

  • @brambos
    Ok, I’ve now done some more in-depth testing.

    Fresh reboot. Background app refresh off. No other apps running.
    New AUM preset, only Mozaic loaded with Flow, empty pattern.

    In this snippet:

      for _pattern = 0 to 6 
        if (mode_multi_pattern = 0 and active_pattern=_pattern) or (mode_multi_pattern = 1 and playing_patterns[_pattern] = YES)
          _note_part = (_pattern * sizeof_pattern) + note_part
          _ch = pattern_midich[_pattern]
    
          call @OnMetroPulse_CC_automation
    
          if voice1[_note_part] >= 0 and ((Random 1, 100) <= note_probability[_note_part])
            call @OnMetroPulse_notes
          endif
        endif
      endfor
    

    Without notes recorded, the last IF is false. If I inline the implementation of @OnMetroPulse_notes the DSP hovers around 27%. If I have call there instead, it drops to 14%.

    Consistently. I’ve added/removed the code several times, and timing is consistent and stable.

    I agree with ”don’t trust DSP/CPU meters” in general, but this consistent difference in measure is really strange.

    Here are the two patches to compare. Just load them in Mozaic and start the host.

    Call: https://www.dropbox.com/s/s52l9zx9wcusz2b/flow-1.1-call-notes.mozaic?dl=0
    Inline: https://www.dropbox.com/s/asbqq4ug31zs5n1/flow-1.1-inline-notes.mozaic?dl=0

    I would be super-happy if the profiler/debugging environment says that all is fine so I can get on with my life and release CC automation and ratcheting for Flow :smiley:

  • @Peblin
    In your code on github, for each MetroPulse there is a 6 round loop, each testing with an IF if something should happen. So for a max ppqn of 384 thats 1536 * 6 tests.

    Your first IF is a compound expression with and/or/array access. Since AFAIK Mozaic does not support 'IF expression shortcuts', all the sub-expressions are always evaluated.

    In case the first IF is true, to code then executed seems a bit complicated including loops and function calls (Interpolate then also incorporates loops but i didn't check the iteration count)

    I also don't know at which PPQN you 'normally' run for 120bpm 4/4 since you compute the ppqn and i did't have to time to check by running it.

    .

    Regarding

    But, doesn’t // Do what doThis refers result in a lot of testing/IFs anyway to perform the correct operation?

    Yes, there are some tests to find the correct action, that that only happens a) for things needed to be done and b) the IF-cascade has only 4 possible outcomes.
    But you are right, i should change this to do binary partioning to bring down the IF count to two 2 for all 4 cases using nested IFs

  • @_ki said:
    @Peblin
    In your code on github, for each MetroPulse there is a 6 round loop, each testing with an IF if something should happen. So for a max ppqn of 384 thats 1536 * 6 tests.

    Your first IF is a compound expression with and/or/array access. Since AFAIK Mozaic does not support 'IF expression shortcuts', all the sub-expressions are always evaluated.

    In case the first IF is true, to code then executed seems a bit complicated including loops and function calls (Interpolate then also incorporates loops but i didn't check the iteration count)

    I also don't know at which PPQN you 'normally' run for 120bpm 4/4 since you compute the ppqn and i did't have to time to check by running it.

    PPQN is computed to be 8x whatever the step size is. So for 1/4th steps, PPQN is 8. Or one pulse every 60ms or so.

    Ie - my 6 IFs should not have this impact. And maybe they don't, if Bram is correct :smile:

    Have a look at my presets above if you want to verify/invalidate my observations :)

  • Midihub Standalone MIDI Processor & Router Lets You Slice & Dice MIDI In Creative Ways

    Now imagine if this device could use the Mozaic language on there device:

    http://www.synthtopia.com/content/2019/08/29/midihub-standalone-midi-processor-router-lets-you-slice-dice-midi-in-creative-ways/

  • I pre-ordered this. My goal would be send midi from iPad/Mozaic/other apps into hardware. I like how simple and visual the editor is.

  • @Bryan I originally posted the Olafur Arnalds info, so excited to try out Clusters! :)

    Unfortunately the Shift issue means I can’t access the Expert mode currently :/ but hoping this gets resolved soon.

    Will I be able to do something similar to this vid at 16:50?

    @echoopera @auxmux (MIDI Hub)

  • Midihub looks interesting, but an ipad + midi interface + plugins is more powerful and flexible so I don't see the advantage unless you prefer a more minimal footprint :)

  • Had a little play with a script based on a description of p-locks by @Samu in another thread.

    https://patchstorage.com/joc-p-lock-symphony/

    Not sure I have figured out state saving yet. Will have revisit that when I get time.

    Otherwise it seems quite serviceable, if I understood the concept correctly..?

  • Hi
    I want to learning . Do you help me , even if my questions are many ?
    Because i want create patch . And I want to break it down into smaller components .And make it into logical language .

  • If I'm going to be using my iPad anyway in my rig, I don't think I'd need MIDIHub. Just got the meeblip go, which is not as powerful but doesn't need to be, and similarly takes very little space.

    If I'm playing a show and I need to adjust something on MIDIHub, I'd have to borrow somebody's computer I think. This is where a dumber box like the meeblip has an advantage - all the MIDI reprogramming can just be done on the iPad.

  • @Artefact2001 said:
    @Bryan I originally posted the Olafur Arnalds info, so excited to try out Clusters! :)

    Unfortunately the Shift issue means I can’t access the Expert mode currently :/ but hoping this gets resolved soon.

    Will I be able to do something similar to this vid at 16:50?

    @echoopera @auxmux (MIDI Hub)

    Yes, Clusters does something very similar to that, although the way it works is a little different. Clusters utilizes Euclidean rhythms, which is how I understand Olafur Arnalds’ software works. I think Clusters is pretty versatile, and I’ve tried to demonstrate that versatility in the factory presets. So you might play around with the presets until the shift issue gets worked out.

    BTW, I just received an email from Bram in which he says that a bug fix for the shift issue is on its way!

  • @brambos

    I have a suggestion.
    I was reading with interest the discussion about GLOBAL variables. Since they are accessible to all scripts, making their use somewhat dangerous in some scenarios, why not introduce the concept of lockable GLOBALS? These would only be accessible via reserve, release, read, and write function calls, with the first argument being a key code and the second a variable name, so that Mozaic can obfuscate the GLOBAL usage.
    The idea would be that changes to the lockable GLOBAL variable would only be allowed if the key code and variable name matched the ones used to request the GLOBAL variable at the start of the script.
    Developers who need access to lockable GLOBALS from different scripts can just use the same key code and variable names in all the scripts in the suite.
    Key codes would need to be unique to a suite, variable names needn’t be.
    All the access functions could return an error code on access failure.

    I haven’t learned the Mosaic syntax yet, but in C style syntax I’m thinking something like this.

    result = reserveLGLOB(key, name); // Checks that no instance exists of lockable GLOBAL variable matching key and name and reserves whatever lockable GLOBAL variable is available using key and name allowing it to be found again when needed. Returns success or error.

    result = releaseLGLOB(key, name); // Frees up the locked GLOBAL matching key and name and makes it available to other scripts. Returns success or error.

    result = writeLGLOB(key, name, data); // Writes data to locked GLOBAL matching key and name. Returns success or error.

    data = readLGLOB(key, name ); // Returns data held in locked GLOBAL matching key and name, or else 0.

    I’m sure I’ll have missed some scenarios, but nothing insurmountable.

  • @TheOriginalPaulB said:
    @brambos

    I have a suggestion.
    I was reading with interest the discussion about GLOBAL variables. Since they are accessible to all scripts, making their use somewhat dangerous in some scenarios, why not introduce the concept of lockable GLOBALS? These would only be accessible via reserve, release, read, and write function calls, with the first argument being a key code and the second a variable name, so that Mozaic can obfuscate the GLOBAL usage.
    The idea would be that changes to the lockable GLOBAL variable would only be allowed if the key code and variable name matched the ones used to request the GLOBAL variable at the start of the script.
    Developers who need access to lockable GLOBALS from different scripts can just use the same key code and variable names in all the scripts in the suite.

    Nice solution.

    But, for me I think ”somewhat dangerous in some scenarios” nails it. The mere existence of GLOBALs are an unsupported AUv3 hack, and I don’t think it makes sense to be over-engineer the use of them.

    1) A clash is highly unlikely, and I think most sane Mozaic devs would check the most popular scripts and avoid using the same GLOBALs
    2) The easy fix is to just change variable, tell the dev and hope that next version of the script won’t clash.

    If anything, it would be amazing if Mozaic could have ByReference variables so scripts could declare globals in OnLoad, such as

      myGlobal = GLOBAL42
    

    making it trivial to just change the global in the event of a clash.

    ByReference would also make it very easy to for example switch between arrays storing midi data - like

    playing_pattern = midi_pattern1_array
    first_note = playing_pattern[0]
    
  • Oh, there’s a limit? :scream:

    Time to refactor... :smiley:

  • edited August 2019

    What are you guys doing running into those limits?? :o

    Realtime environments do not allow dynamic memory allocation, so the concept of unlimited doesn’t really fly in this context. Hence I am practically forced to work within set limits.

  • @brambos said:
    What are you guys doing running into those limits?? :o

    Realtime environments do not allow dynamic memory allocation, so the concept of unlimited doesn’t really fly in this context. Hence I am practically forced to work within set limits.

    :smiley:

    This is probably the main "culprit": https://github.com/peblin/flow-sequencer/blob/7d9bd601a58fd2db676b0582215eb97a53a03fc7/flow-sequencer.script#L17

    Excerpt:

      // Buttons
      BTN_MULTI_PATTERN = 0
      BTN_RECORD = 1
      BTN_MIDI = 2
      BTN_QUANTIZE = 3
      BTN_TRANSPOSE = 4
      BTN_COPY_PATTERN = 5
      BTN_CLEAR_ALL = 6
      BTN_CLEAR_ARMED = 7
      BTN_HELP = 15
    

    I want to use constants for buttons/knobs/pads, otherwise the code just gets impossible to maintain. So, a constants in Mozaic would definitely solve that part of the problem.

    Flow is now also up to ~30 arrays for storing pattern data, so with all the constants and arrays the rest of the script (1700+ lines now) only has around 100 variables left.

  • Hence my suggested method. All available lockable GLOBALS would be preallocated by Mozaic like the non lockable ones are now.

  • @brambos
    Thanks for the update! Sysex looks promising.
    I'm not sure if it's a problem on my end, but when I follow the link to the manual it will open v1.2 in a browser (chrome), but when I'm saving it ("copy to PDF Pro" or "Save to Files") it's v1.0.
    Can someone confirm this please.

Sign In or Register to comment.