New Tool (3.5) cycler

cycler

cycler_test

A small popup editor that lets you generate regular pattern in the Pattern Editor via cycle strings and regular pattrns scripts.

Provides one key-binding in Pattern Editor / Tools

  • Open cycler window

You can also open the window from the right-click menu of the Pattern Editor.

usage

There are two modes the cycler window can be in

  • edit mode is where you can edit the code and have it render to the pattern
  • select mode is for navigating the song without closing the window

edit mode

  • the text editor field should be focused
  • type in some cycle expression, like <a2 g2 c3> [- <f3 d3 g3>*2], <c3 e3 d3>
  • the pattern will be automatically overwritten
  • if there are errors in your code, they will be shown below
  • press escape to switch to select mode

select mode

  • switch between different tracks with the left and right keys
  • select instruments to be used by default for the cycle’s output with up and down (if you specify an instrument in the cycle via c:#2 that will be kept)
  • go to other patterns in the sequence by holding alt and pressing up and down
  • you can go back to editing by pressing enter
  • escape will close the window

raw script

You can choose between cycle and raw below the script

  • cycle is the default generation method, it will essentially paste your text into a Phrase Script like return cycle("YOUR_TEXT_HERE"), so that you don’t have to type in the wrap around the string, nor the quotes.
  • raw mode lets you write a regular Phrase Script

vim keys

When in select mode, you can use h j k l instead of the arrows

limitations

cycler tries to remember your scripts on a per-pattern and per-track basis, but this is implemented in a rudimentary fashion, if you are reordering or adding new tracks or pattern, expect your scripts to me jumbled or even lost.

download

see source
donate :heart:

13 Likes

Forgot to actually push to git, source should be inside my repo now.

I tried a script that works in the phrase script editor, but gives an error in your tool. Is there a compatibility issue over a certain amount of characters?

script;

return pattern {
  parameter = {
parameter.enum("scale1", "Chromatic", {
  "Algerian", "Altered Dominant", "Arabian", "Arabian Hijaz", "Augmented", "Balinese", 
  "Balinese Pelog Selisir", "Blues Major", "Blues Minor", "Byzantine", "Chromatic", 
  "Chinese", "Diminished Half", "Diminished Whole", "Double Harmonic", "Dorian", 
  "Enigmatic", "Harmonic Major", "Harmonic Minor", "Harmonic Minor Inverse", "Hungarian Gypsy", 
  "Hungarian Major", "Indian Todi", "Iwato", "Japanese", "Japanese In Sen", "Kumoi", 
  "Lydian", "Lydian Augmented", "Lydian Dominant", "Locrian", "Locrian Major", "Melodic Minor", 
  "Messiaen Mode 1", "Messiaen Mode 2", "Messiaen Mode 3", "Mixolydian", "Natural Major", 
  "Natural Minor", "Neapolitan Major", "Neapolitan Minor", "Nine-Tone", "Pentatonic Egyptian", 
  "Pentatonic Major", "Pentatonic Minor", "Phrygian", "Phrygian Dominant", "Prometheus", 
  "Pelog", "Pelog Bem", "Romanian Minor", "Ryukyu", "Spanish Eight-Tone", "Spanish Gypsy", 
  "Spanish Phrygian", "Super Locrian", "Tritone", "Tritone Dominant", "Whole Tone", 
  "Yo Scale"
}, "Scale 1"),

    parameter.integer("steps1", 16, {3, 32}, "Steps per rhythm (Scale 1)"),
    parameter.integer("pulses1", 1, {1, 16}, "Pulses per rhythm (Scale 1)"),
    parameter.integer("variation1", 1, {1, 100}, "Variation (Scale 1)"),

parameter.enum("scale2", "Chromatic", {
  "Algerian", "Altered Dominant", "Arabian", "Arabian Hijaz", "Augmented", "Balinese", 
  "Balinese Pelog Selisir", "Blues Major", "Blues Minor", "Byzantine", "Chromatic", 
  "Chinese", "Diminished Half", "Diminished Whole", "Double Harmonic", "Dorian", 
  "Enigmatic", "Harmonic Major", "Harmonic Minor", "Harmonic Minor Inverse", "Hungarian Gypsy", 
  "Hungarian Major", "Indian Todi", "Iwato", "Japanese", "Japanese In Sen", "Kumoi", 
  "Lydian", "Lydian Augmented", "Lydian Dominant", "Locrian", "Locrian Major", "Melodic Minor", 
  "Messiaen Mode 1", "Messiaen Mode 2", "Messiaen Mode 3", "Mixolydian", "Natural Major", 
  "Natural Minor", "Neapolitan Major", "Neapolitan Minor", "Nine-Tone", "Pentatonic Egyptian", 
  "Pentatonic Major", "Pentatonic Minor", "Phrygian", "Phrygian Dominant", "Prometheus", 
  "Pelog", "Pelog Bem", "Romanian Minor", "Ryukyu", "Spanish Eight-Tone", "Spanish Gypsy", 
  "Spanish Phrygian", "Super Locrian", "Tritone", "Tritone Dominant", "Whole Tone", 
  "Yo Scale"
}, "Scale 2"),

    parameter.integer("steps2", 16, {3, 32}, "Steps per rhythm (Scale 2)"),
    parameter.integer("pulses2", 1, {1, 16}, "Pulses per rhythm (Scale 2)"),
    parameter.integer("variation2", 1, {1, 100}, "Variation (Scale 2)"),

    parameter.integer("seed", 123456, {0, 999999}, "Random Seed"),
  },
  unit = "1/4",
  pulse = function(context)
    -- Generate random states for both scales separately
    local rand1 = math.randomstate(math.floor(context.parameter.seed) + context.parameter.variation1)
    local rand2 = math.randomstate(math.floor(context.parameter.seed) + context.parameter.variation2)

    -- Generate rhythms for both scales independently
    local pattern1 = pulse.euclidean(context.parameter.pulses1, context.parameter.steps1, 0)
    local pattern2 = pulse.euclidean(context.parameter.pulses2, context.parameter.steps2, 0)
    
    -- Combine both patterns (they can play independently in parallel)
    return {pattern1, pattern2}
  end,
  event = function(context)
    -- Scale definitions
    local scales = {
      ["Chromatic"] = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59},
      ["Natural Major"] = {48, 50, 52, 53, 55, 57, 59},
      ["Natural Minor"] = {48, 50, 51, 53, 55, 56, 58},
      ["Pentatonic Major"] = {48, 50, 52, 55, 57},
      ["Pentatonic Minor"] = {48, 51, 53, 55, 58},
      ["Phrygian Dominant"] = {48, 51, 52, 55, 57, 59, 61},
      ["Locrian Major"] = {48, 50, 52, 53, 55, 57, 59},
      ["Super Locrian"] = {48, 50, 51, 53, 54, 56, 58},
      ["Neapolitan Major"] = {48, 49, 52, 53, 55, 57, 59},
      ["Neapolitan Minor"] = {48, 49, 52, 53, 55, 56, 59},
      ["Romanian Minor"] = {48, 50, 52, 53, 55, 57, 60},
      ["Spanish Gypsy"] = {48, 50, 51, 53, 55, 57, 60},
      ["Hungarian Gypsy"] = {48, 50, 52, 54, 55, 57, 60},
      ["Enigmatic"] = {48, 49, 52, 53, 56, 57, 59},
      ["Overtone"] = {48, 50, 52, 54, 56, 58, 60},
      ["Japanese In Sen"] = {48, 50, 53, 55, 60},
      ["Hirajoshi"] = {48, 50, 52, 55, 58},
      ["Yo Scale"] = {48, 50, 53, 55, 60},
      ["Pelog"] = {48, 49, 51, 52, 54},
      ["Balinese"] = {48, 49, 51, 53, 55},
      ["Pentatonic Egyptian"] = {48, 50, 53, 55, 58},
["Blues Major"] = {48, 50, 51, 52, 55, 57},
["Blues Minor"] = {48, 51, 53, 54, 55, 58},
["Whole Tone"] = {48, 50, 52, 54, 56, 58},
["Augmented"] = {48, 52, 56, 60},
["Prometheus"] = {48, 50, 52, 54, 55, 58},
["Tritone"] = {48, 50, 53, 54, 57, 58},
["Harmonic Major"] = {48, 50, 52, 53, 55, 56, 59},
["Harmonic Minor"] = {48, 50, 51, 53, 55, 56, 59},
["Melodic Minor"] = {48, 50, 51, 53, 55, 57, 59},
["All Minor"] = {48, 50, 51, 53, 55, 56, 58, 59},
["Dorian"] = {48, 50, 51, 53, 55, 57, 58},
["Phrygian"] = {48, 49, 51, 53, 55, 56, 58},
["Diminished Half"] = {48, 50, 51, 53, 54, 56, 57, 59},
["Diminished Whole"] = {48, 49, 51, 53, 54, 56, 58, 60},
["Spanish Eight-Tone"] = {48, 49, 51, 52, 53, 55, 56, 58, 59},
["Nine-Tone"] = {48, 49, 50, 51, 52, 53, 54, 55, 56, 57},
["Double Harmonic"] = {48, 49, 52, 53, 55, 56, 59},
["Persian"] = {48, 49, 52, 53, 54, 57, 58},
["Byzantine"] = {48, 49, 52, 53, 55, 56, 59},
["Algerian"] = {48, 50, 51, 54, 55, 56, 59, 60},
["Hungarian Major"] = {48, 50, 51, 54, 55, 57, 59},
["Harmonic Minor Inverse"] = {48, 50, 52, 53, 56, 57, 59},
["Japanese"] = {48, 49, 52, 55, 56},
["Chinese"] = {48, 50, 52, 55, 57},
["Indian Todi"] = {48, 49, 52, 53, 55, 56, 58},
["Arabian"] = {48, 49, 52, 53, 55, 57, 58},
["Slovakian"] = {48, 50, 52, 53, 55, 56, 58},
["Messiaen Mode 1"] = {48, 50, 52, 54, 56, 58, 60},
["Messiaen Mode 2"] = {48, 50, 51, 53, 54, 56, 57, 59},
["Messiaen Mode 3"] = {48, 49, 50, 52, 53, 54, 56, 57, 58, 60},
["Tritone Dominant"] = {48, 50, 52, 54, 56, 57, 58},
["Lydian Dominant"] = {48, 50, 52, 54, 56, 57, 59},
["Altered Dominant"] = {48, 49, 51, 53, 54, 56, 58},
["Spanish Phrygian"] = {48, 49, 51, 52, 55, 57, 58},
["Balinese Pelog Selisir"] = {48, 49, 52, 53, 55},
["Pelog Bem"] = {48, 50, 52, 53, 55},
["Iwato"] = {48, 49, 52, 54, 57},
["Ryukyu"] = {48, 50, 53, 55, 58},
["Arabian Hijaz"] = {48, 50, 51, 54, 55, 57, 58},
["Kumoi"] = {48, 50, 51, 55, 57},
["Maqam Rast"] = {48, 50, 52, 53, 55, 57, 59},
["Maqam Bayati"] = {48, 49, 51, 53, 55, 57, 58},
["Maqam Saba"] = {48, 49, 51, 52, 55, 57, 58},
["Maqam Nahawand"] = {48, 50, 51, 53, 55, 56, 58},
["Maqam Sikah"] = {48, 49, 52, 53, 56, 57, 60},
["Mixolydian"] = {48, 50, 52, 53, 55, 57, 58},
["Lydian Augmented"] = {48, 50, 52, 54, 56, 57, 59},
["Lydian"] = {48, 50, 52, 54, 55, 57, 59},

      -- Add more scales as needed
    }

    -- Select scales
    local selected_scale1 = scales[context.parameter.scale1] or scales["Chromatic"]
    local selected_scale2 = scales[context.parameter.scale2] or scales["Chromatic"]

    -- Random generators
    local rand1 = math.randomstate(math.floor(context.parameter.seed) + context.parameter.variation1)
    local rand2 = math.randomstate(math.floor(context.parameter.seed) + context.parameter.variation2)

    -- Create note sequences
    local note_sequence1 = pulse.new(context.parameter.steps1):map(function(_)
      return selected_scale1[rand1(#selected_scale1)]
    end)

    local note_sequence2 = pulse.new(context.parameter.steps2):map(function(_)
      return selected_scale2[rand2(#selected_scale2)]
    end)

    -- Return combined notes
    local step_in_sequence1 = math.imod(context.step, #note_sequence1) + 1
    local step_in_sequence2 = math.imod(context.step, #note_sequence2) + 1

    return {
      {
        key = note_sequence1[step_in_sequence1] or 48,
        volume = rand1(80, 100) / 100,
        panning = rand1(-50, 50) / 100,
        delay = rand1(0, 10) / 100,
      },
      {
        key = note_sequence2[step_in_sequence2] or 48,
        volume = rand2(80, 100) / 100,
        panning = rand2(-50, 50) / 100,
        delay = rand2(0, 10) / 100,
      },
    }
  end,
}

In your tool window it says;

syntax error: [string "Phrase Script"]:168: unexpected symbol near '<eof>'

Interesting, it seems that if you change the text a bit after pasting (for example adding a newline at the end), it will work. Not sure what is going on with that, but I’ll look into it later.

Honestly, I left raw mode in mostly for testing and being able to remap a cycle if needed. Given that the text editor here has neither syntax highlight, nor autocomplete, complex scripts like this will not be a joy to edit (would also need to make the editor size configurable).

1 Like

version 0.2 - hotfix

  • fixing some issues with cycler still listening to track change events after the window is closed (and changing instruments)
  • sometimes the script was cleared unexpectedly, this should be also be fixed now
  • added notes on vim keys to readme
2 Likes

oh this is a super cool tool.

Is it possible to make the default note for numeric input be the note 0 before the 4th round?

and is it also possible to add the ability to select an octave separately?

the idea is to control the change of octaves not only per step but also per cycle. by writing a chord we prescribe octave changes

I discovered a nuance: if you select another instrument within a phrase, but the phrase itself still refers to the index of the first instrument, then the change does not occur.

[[0 [1]:#1] 0 0 0]:#0

Thanks!

Not sure what you mean by the octaves comment, but transposing octaves based on your current keyboard octave is something I might add as an option.

If you mean sequencing the octave with another cycle pattern like you’d do in tidal by combining patterns then no, this tool will not implement such complex features. This is something that should be done on the Phrase Script library level.

I think you mean changing 0 1 2 to result in c4 c#4 d4 instead of c0 c#0 d0, if so then this would be possible via the octave transpose feature mentioned above, you’d need to set your keyboard octave to 8 to get this result.

Yes, this is intentional and it lets you control multiple instruments with a single cycle. If you want your currently selected instrument to be used for all notes for the output, just don’t include # properties inside the cycle.

Maybe similar to the octave transpose idea above, instruments could work in “relative mode” as well so c:#1 would result in your currently selected instrument +1, but this might be more confusing than useful and there would be no way to go negative.

I understand what you mean. Just a note up to 4 octaves is a basic template for playing any instrument. In this sense, I think the number 0 just corresponded to this standard. And as for the digital display of notes, when creating chords, you can use digital values ​​to create chords based only on numbers. This helps those who are poorly oriented in keyboard values ​​and use pure mathematics.

It also seems to me that it would be useful to add the number of repetitions. That is, the template could immediately create several patterns. Firstly, it is convenient and visually within the code to control longer parts. This is approximately how I would see it in the form of code.

pt:4 < — numbers of pattern
n [0 0 3 3 4], [ 3 3 7 12 12]. <— notes
ot[4 4 3 0.7]/4 <---- octaves

or

pt:4 < — numbers of pattern
n:3 [0 0 3 3 4], [ 3 3 7 12 12]. <— notes (n:3) number octave

upd

and I really need a button or key combination to run the script from the cycler.

and it would also be useful to add the command

trk:1 trk:2 etc

trk:1 ← number of track
pt:4 <— numbers of pattern
n [0 0 3 3 4], [ 3 3 7 12 12]. <— notes
ot[4 4 3 0.7]/4 <— octaves
trk:2. ← number of track
n:3 [0 0 3 3 4], [ 3 3 7 12 12] <— notes with base octave

and write a script with several tools at once.

Note, this tool won’t change the syntax and language features of the pattrns engine, it is simply meant to provide a fast way to render phrases to main patterns and does no parsing on its own.

The only transformation it does is assigning the current instrument to notes that have no instrument specified. The same thing could be done for octaves but in a much more static fashion than you envision. Making up new rules for the mini-notation is outside of the scope of this tool.

One thing I want to add is another rendering mode that generates automatic remap of names based on instrument/sample names but this is a bit more complex and needs more consideration and work.

I’ll add a setting to toggle between manual and automatic script rendering.

1 Like

Yes, I understand that of course. This is my feedback and the functions that I definitely used regularly and would increase my efficiency. Plus this is the experience of using tidal cycles.

my experience

1 Like

version 0.3

  • keys not doing anything in select mode are now relayed to Renoise, so you can for example start the song with space or use any of your existing keybindings without having to defocus the window (apart from what is hardcoded in cycler)
  • fixed the issue with some special characters throwing incorrect errors in cycle notation
1 Like

caught a bug

spent some time with the tool. honestly, it would be very convenient to input notes with numbers. but the problem of no octave switching reduces its effectiveness. and it would be great to add the ability to write a script for several patterns in advance. also, you can add 3 areas for a notepad where you can store notes from other projects with comments.

1 Like

Thanks for the bug report!

I did play around with octave features and I might add it later but it needs more work. Until then, you can transpose your instrument higher to be able to use numbers as notes.

Thought about several patterns in one script but this would make the tool deviate more from Phrase Scripts and have its own syntax to express this, parsing etc. I doubt this will be a feature anytime soon as it would require the tool to work fundamentally differently.

I do want to add comment lines to the cycle mode text. Not sure about what you mean by 3 notepads to store stuff in though.

1 Like

I meant to store some of the templates with comments in a notebook.

@unless Hello! Any news? Really looking forward to it!

Cheers!

Currently taking a break from Renoise stuff, will definitely post here if the tool has updates.

3 Likes