Quintessence

i think the docs are a little unclear on this.

summary:

  • engines are supercollider Classes, and supercollider cannot successfully compile the class library if it sees multiple Classes with the same name. (in this case, Engine_PolySub)

  • Engine_PolySub is defined in Engine_PolySub.sc, which is provided in the we repo. (this is sort of an old grab-bag of stuff that really should maybe just be included in the main norns/sc path; oh well.)

  • so, you have two options:

      1. make it clear that your script has a dependency on we. we don’t have a full-fledged package/dependency management system on norns; user will simply have to install it separately. (@SPIKE, i’m surprised you don’t have we installed already?)
      1. make a “fork” of the engine and include it in your project’s /lib; called Engine_Quintessence or whatever. (by convention, if a SC class file contains a single class definition, then the file name should match the class name; but NB that supercollider only cares about class name collisions and doesn’t actually care about filenames. [it’s a common pattern to group related classes in a single file actually.])
      1. ah, worth noting that there is also a polysub.lua (by convention) which provides a “parmeter factory” module for the assocated engine.

the bonus of “forking” the engine is that you can tweak it to your taste if you want to get into that side of things and you haven’t yet. (it is easy and rewarding.)

uh, hope that makes sense and lmk if it doesn’t. i know it’s not an ideal situation.

(@dan_derks or whoever, this information might be worth capturing more clearly on a docs page somewhere, but i dunno how best to do that. like “how to make a script using an existing engine” micro-tutorial)

4 Likes

OH! I thought PolySub was included by default - that makes much more sense. I’ll choose one of the default ones instead. Thanks!

well, it sort of is – the base image includes we, but if somebody removes that folder then PolySub would be missing. @SPIKE , curious to hear more detail, if you have it :slight_smile:

this is a good idea, thanks @zebra !

2 Likes

A quick update on my progress here. I did not get a whole lot done last week, since I was doing other things like hanging out on the beach and getting a sunburn - appropriate California activities. That said, I did test out a PolyPerc-based mode for Quintessence, and I quite like it. My roadmap at the moment is as follows:

DONE:

  1. Passthrough integration
  2. Send MIDI transport (MMC) to sync run state with other gear (in my case, Pam’s). When pressing K2 (toggle RUN) it’ll optionally output MMC Play (0x02) or MMC Pause (0x09); when pressing K3 (reset dt) it’ll optionally output MMC Stop (0x01), followed immediately by MMC Play if the sequencer is running. That way, pausing/playing and resetting the sequence can, if you want, also pause/play and reset the position in your modular or drum machine sequences.
  3. Basic Norns engine mode

TO DO:

  1. Norns engine mode with a parameter mod matrix, so that the waveforms from the distances ab, bc, and ca can be used to modulate timbre parameters. Please let me know what engine you’d like to see; I considered Mi-Engines but the installation process looks a bit hairy.
  2. MIDI pitch-bend output and MIDI CC output.
  3. A more robust workflow for value sampling; each waveform will be able to be sampled and held based on close/far approaches, tempo-synced intervals (1/16th notes to whole notes? let me know if you want more range), or continuous sampling.
  4. A more robust workflow for quantization; I may simply borrow the code from Passthrough, or perhaps I’ll write my own, but either way the goal is to quantize resting points of values at the time they are held by the sample and hold step.
  5. A more robust workflow for trigger generation. Right now, triggers are generated once every sixteenth note when bodies are close or far; this is fine, but what should really happen by default is that a NOTE ON x should be generated for each close approach, PITCH BEND should be applied to change the value if desired, and NOTE OFF x should be generated when the close approach is over. This will also include tuning the distance tolerances for close approaches, perhaps per body. EDIT: Also, triggers per MIDI channel, so different close approaches can trigger different instruments (maybe).

Please let me know your thoughts on this roadmap and especially anything you think I’ve failed to consider!

11 Likes

Oh hell yes for the rings module :sweat_smile: :innocent:

1 Like

i was crazy excited there for a sec…i thought this was a full on update post at first! :stuck_out_tongue:

question…
will it have all these same MMC for the incoming MIDI?
i am almost always syncing norns to MIDI clock from external gear.

1 Like

Hah, sorry, not quite done with all this yet. But, I can absolutely make it accept incoming MMC too!

EDIT: Argh, my poor knowledge of the MIDI spec strikes again. MMC is not the right thing for this. Instead, stopping the sequencer will always send a MIDI Beat clock Stop (what Norns’s clock responds to), and depending on whether you’ve reset or not, starting it will produce MIDI Beat clock Start (start at the beginning) or Continue (pick up where you left off). Not all gear supports this correctly, including a lot of modular stuff.

2 Likes

Updated to v0.0.2. I’ve added toggleable support, both ways, for MIDI transport; also, there are now toggles for the use of both MIDI and the PolyPerc audio engine.

Do note that if you want to use MIDI transport output, you need to turn PARAMETERS/CLOCK => midi out to off. Quintessence passes Norns’ clock through.

3 Likes

Reporting weird behavior on my norns: when the line passes the dot in the center it triggers a bunch of notes in a row. And also triggers sometimes when it does not touch the dot.

Other than that, excited AF about this cool script :smiley:

1 Like

These are both expected, though triggering a bunch of notes is not what I want long term.

So, the “make 16th notes” state is activated whether any two bodies are at their closest or furthest extension. Furthest does mean that they have to be on opposite sides of the sun (dot), but closest doesn’t!

I’ll probably save this behavior behind a toggle - “arp mode” or something - but once I have the note tracking working right it’ll sustain the notes as long as the close or far state, or whatever other trigger, is active.

Thank you so much for testing out the script!

1 Like

ah :smiley: I thought that the note would only be triggered once the line crosses the dot

edit: ok now I get it :slight_smile: are you planning to add an option to choose a scale? Does it make sense here?

1 Like

Definitely! Quantization is absolutely on the roadmap, though I’m not sure if there are community conventions around how to do it well. I’ll look into some other scripts first, I think.

1 Like

@Fardles + I are working on the extended ref pages for musicutil which should be merged into reference | monome/docs shortly!

here's a "too much for the docs" example of chaining together musicutil's built-in scale + chord generation, if that might be helpful until the proper merge
MusicUtil = require("musicutil")
engine.name = "PolyPerc"

-- we can extract a list of scale names from musicutil using the following
scale_names = {}
for i = 1, #MusicUtil.SCALES do
  table.insert(scale_names, MusicUtil.SCALES[i].name)
end

playing = false -- whether notes are playing

function init()
  engine.release(2)

  -- setting root notes using params
  params:add{type = "number", id = "root_note", name = "root note",
    min = 0, max = 127, default = 60, formatter = function(param) return MusicUtil.note_num_to_name(param:get(), true) end,
    action = function() build_scale() end} -- by employing build_scale() here, we update the scale

  -- setting scale type using params
  params:add{type = "option", id = "scale", name = "scale",
    options = scale_names, default = 5,
    action = function() build_scale() end} -- by employing build_scale() here, we update the scale
  
  -- setting how many notes from the scale can be played
  params:add{type = "number", id = "pool_size", name = "note pool size",
    min = 1, max = 32, default = 16,
    action = function() build_scale() end}

  build_scale() -- builds initial scale
end

function build_scale()
  notes_nums = MusicUtil.generate_scale_of_length(params:get("root_note"), params:get("scale"), params:get("pool_size")) -- builds scale
  notes_freq = MusicUtil.note_nums_to_freqs(notes_nums) -- converts note numbers to an array of frequencies
  local rnd = math.random(1,#notes_nums) -- a random integer
  current_note_num = notes_nums[rnd]
  chord_options = MusicUtil.chord_types_for_note(current_note_num,params:get("root_note"),params:string("scale"))
end

function new_note()
  local rnd = math.random(1,#notes_nums) -- a random integer
  current_note_num = notes_nums[rnd] -- select a random note from the scale
  current_note_name = MusicUtil.note_num_to_name(current_note_num,true) -- convert note number to name
  arp_note = nil
  redraw()
end

function play_chord(random_seed)
  if random_seed then
    random_chord = math.random(1,#chord_options)
    chord_notes = MusicUtil.generate_chord(current_note_num,chord_options[random_chord])
  end
  playing = true
  for i = 1,#chord_notes do
    clock.sleep(0.15)
    arp_note = MusicUtil.note_num_to_name(chord_notes[i],true)
    engine.hz(MusicUtil.note_num_to_freq(chord_notes[i]))
    redraw()
  end
  playing = false
end

function key(n,z)
  if n == 2 and z == 1 then
    if not playing then
      new_note()
      clock.run(play_chord,true)
    end
  elseif n == 3 and z == 1 then
    if not playing then
      clock.run(play_chord,false)
    end
  end
  redraw()
end

function redraw()
  screen.clear()
  screen.level(14)
  screen.move(64,32)
  if playing == true then
    screen.font_size(24)
    screen.text_center(current_note_name) -- display the name of the note that is playing
    screen.font_size(8)
    if arp_note ~= nil then
      screen.move(128,50)
      screen.text_right("arp: "..arp_note)
    end
    screen.move(128,60)
    screen.text_right("chord: "..chord_options[random_chord])
  else
    screen.font_size(8)
    screen.text_center("press k2 to play")
  end
  screen.update()
end
3 Likes

Absolutely lovely, thank you!

1 Like

@NoraCodes
AMAZING update!

i had a little trouble after install and restart.
it wasn’t reacting to MMC and when i did get it to react…it locked up.
did another reboot from SLEEP and everything started working great!
(MIDI start/stop and all)

here’s last night’s noiz session using Quintessence with @dwtong Glaciers (with the awesome live input recording) :stuck_out_tongue: and @dan_derks Cheat Codes 2.
Quintessence is driving a Virus B.
some other noiz help with a couple of Leploops and a Pulsar-23.

3 Likes

Thanks, I’m so glad it’s working for you! I love listening to your sessions :slight_smile:

1 Like

Hey ya!
another small request… :stuck_out_tongue:
could you make the settings under the EDIT parameters page persist?

i have to go back and set the receive transport every time i launch the script.

1 Like

Yep, I need to add a call to the top of the script to do that, I think. I’ll stick it in the next release.

I’m in person all week at the office this week, so not much time to work on Norns stuff. Next week is wide open, though, so hopefully I can churn through some of the roadmap!

2 Likes

I’ve gone ahead and added this, as well as Passthrough integration, in the latest update.

4 Likes

It makes sense to me to send MIDI CC (I’m modifying this to send CC both from the distances as well as the area), and I believe I found a a little :beetle: in lib/14tet.lua line 10, which is

return FREQ_A4 * (TWELFTH_ROOT_OF_2 ^ (note - NOTE_A4))

but might mean to be

return tuning.FREQ_A4 * (tuning.TWELFTH_ROOT_OF_2 ^ (note - tuning.NOTE_A4))

I am not sure if these kinds of things would be best routed via GitHub as issues and perhaps pull requests, or here on lines. It’s a bit technical…

1 Like