-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Mapping identifiers and numbers to notes in Cycles #13
Comments
My wishes were around scales, either with some cycle("0 2 [4 5] [6 4]"):scaled("minor"):transposed("d") Or have the scale object contain common mapping functions that can be used without lambdas, I like this a bit more because you can define a scale at one place and reuse it. local cmin = scale("penta", "c")
cycle("0 2 [4 5] [6 4]"):map(cmin.notes)
cycle("0 2 [4 5] [6 4]"):map(cmin.chords) It might be nice to also support something like local cmaj = scale("major", "c")
cycle("ii V I"):map(cmaj.chords) But the issue with these approaches I think is that just taking the chords from scales is not very interesting, you often want to invert a chord, extend it or use something out of the scale at some point. If there was some more general chord mapper that would be really handy. For example we could make use of the cycle("c4:m7 f3:7#11 c3:M9i1"):as_chords() |
I like this one: local cmin = scale("penta", "c")
cycle("1 2 [4 5] [6 4]"):map(cmin.notes)
cycle("1 2 [4 5] [6 4]"):map(cmin.chords) This would work out of the box with the suggested mapping feature.
Actually would be nice if the existing note chord's syntax could be directly used in cycle as well. I had borrowed that from Tidal already. afseq/types/nerdo/library/note.lua Line 23 in 982dee3
cycle("c4'm7 f3'7#11 c3'M9") I guess this could be added directly to the cycle impl - without any custom mapping? Good point about the Both should be passed as arguments to a map lambda: local cmin = scale("c", "minor")
local function my_scale_map(value, target)
local degree, note_count = value, target
return cmin:chord(degree, note_count)
end
cycle("I:3 V:3 IIV:5"):map(my_scale_map) quite verbose, but I personally don't have a problem with that. |
With the basic mapping working, I'll start looking into automatically mapping chords as local cmin = scale("c", "minor")
cycle("i v vi iv"):map(function(context, degree)
return cmin:chord(degree)
end) Once that's done, we can start thinking about how to make some of the more common things, like chords, easier to map. |
Started experimenting with this now here: Example: cycle("[1 5 6 <_ 4>]/4"):map(
mappings.chord_from_degrees(scale("a", "major"))
) Idea is to provide a set of functions which return cycle map functions here. This is hard to generalize. Also mappings currently can't be stacked, which would be nice. This could be solved in plain lua or by allowing multiple mapping callbacks to be passed to the cycle map. @unlessgames I think you're more after a functional programming approach here. Any ideas? I've also noticed that the event merging in the cycle is counter productive here. In this example, the --- step number 3 in map context here refers to `g4` instead of `_`, because the `_` gets removed in cycle.generate.
cycle("[c4 c5 _ g4]/4"):map(
mappings.with_volume({1.0, 0.5, 0.25})
) Assuming the merging is just cosmetics or optimization, we should disable, remove it again. |
It might be better to add such functionality to the cycle implementation as that would open up more possibilities and also solve the issues with the mapping you mention below (both stacks and holds/rests would be treated by it). In tidalcycles you can combine mini-notation patterns to achieve mappings like these. Instead of mapping on a per-step basis, it essentially works by generating 2 patterns and overlaying them. It's a bit counter-intuitive at first but overall quite powerful way to compose. For example the above examples would generate two patterns (assuming the second is
When overlapped like this, c4 and c5 would get 1.0 as volume, g4 would get 0.25. (In tidal you'd write this as Similar overlapping logic will be implemented in cycle.rs anyway for the cases of This would also allow for easy variation on both sides like using
The mappings style you used above is already pretty nice even if the naming is a bit too verbose for my taste. I understand it's a matter of preference but in a livecoding scenario I'd prefer to be able to omit extra words like As an alternative, it might be nice to keep the verbose way but also provide some shorthand that can even by a single char or something. For example tidal aliases That said, in lua these could be better expressed by having these common functions be present on the cycle which could be chained? For example you'd write On that note, since there is already a chord notation syntax present in cycles, maybe the same could be used for scales too so that the user don't have to remember two different vocabularies? For mapping chords and scales, I'd unwrap the transposition somehow and make it a separate operation. So you could map to chords or scales and transpose later, and transpose without scales or chords the same way.
This would make it easier on the eyes to parse expressions because you don't have to crawl into the nested structures to understand where the transposition happens for example as well as allow easy experimentation where you could just split into new-lines over the dots and toggle each operation as comments.
I think this is the correct behaviour as a hold is not really a step in a composition sense, just a way to notate a longer note. In the above example I'd expect the 3rd value to pair with the third note. Otherwise it would be increasingly hard to count steps in more complicated patterns, the same thing applies to rests. It's meaningless to map a rest or a hold to a volume or chords so I think it's good you don't have to include dummy values here to account for these.
Not sure I understand what you are proposing here. We could make holds be applied at parse-time. This would disallow some tricks that are impossible in tidal as well, which might be also desired. For example But overall this won't solve the above issue as the hold "disappears" from the output pattern either way. Do you mean the output events should just contain holds as is? Same for all rests? |
This is indeed easier to read and write. I'll see if and how I can add that. Also agree on shortening the names where possible. I still want to keep the mapping functions as they are, because accessing the Also the freeform mapping
To me, this just makes things harder to understand and to use - in the map function. Else I don't really care. In Tidal, combined expressions like With the map thing, we have to match the steps exactly. And removing steps automatically doesn't make it any easier to write the mappings, as you need to know how exactly they are evaluated and merged down. In this cycle for example: And on the other hand, the merge doesn't really do anything useful. The playback engine doesn't care if two note offs are fired after each other. Hold simply does nothing (it does not stop), so you can either merge them or not without making a difference in the playback.
Yup, exactly. But just to make it easier to map them via the context.step counter. Apropos: instead of mapping, it would be much easier in many cases to define various note properties directly in the cycle. In note strings outside of the cycle, this can now be hackily done with PS: this actually could solve or at least is related to #29 as well... |
Another example where merging doesn't work well with mapping: emit = cycle("[1 3 4 1 3 4 <7 ~>]/7"):map(
mappings.combine(
mappings.intervals(scale("c5", "natural minor")),
mappings.transpose({ 0, 0, 0, 0, 0, 0, -12 })
)
) The I've cleaned up the naming and added Not a fan of skipping the key in Still working on how to make all these things methods of the cycle: emit = cycle("[1 3 4 1 3 4 <7 ~>]/7"):intervals(scale("c5", "natural minor")):transpose({ 0, 0, 0, 0, 0, 0, -12 }) Once we have those, all those mapping.xxx functions actually can be removed. Regarding the ability to specify vol/pan and co in cycles a la |
The gain is overlayed as steps, and it's applied only for discrete steps that come from the "main" pattern, not mid-way between steps as you assume. I think in most cases midway stepping of volume or other parameter changes is undesirable and having to precisely line-up events would be finicky (especially for varying patterns), so this system works nicely in tidal.
The problem here is that it is very common to have patterns with varying step count over different iterations like
I like the idea. But ideally it would be something more general than only these hardcoded parameter types. I wonder if making the name operator |
I'll skip the mapping stuff for now and agree that it would be better to do this by combining cycles instead of using value arrays in mappings. So using e.g. Let's also skip the scale/interval mapping stuff until this is sorted out.
That's nice, and actually how this works in strudel too:
The group behind the second colon defines the velocity in this example. I'd prefer if the target attribute (instrument, volume, panning...) could be addressed directly with a prefix:
which makes it possible to e.g. only access panning or volume without specifying a target instrument. In strudel you have to skip through them via:
With two attributes that's maybe ok, but definitely not if you want to reach the 4th attribute (delay in our case). |
Right now, only note strings and integers are supported in cycle. e.g:
cycle("c4 c5")
orcycle("48 60")
But it's possible to use arbitrary strings as identifiers, and floating point numbers in the mini notation too:
cycle("bd sn")
Such identifiers are currently ignored and will emit nothing.
The easiest and probably most obvious way to map such identifiers to notes (or later other event types) could be using a map function which takes a table or function as argument:
table:
function:
I think 1. could be implemented quite easily using the following rules:
map_with_scale
funtions for that.@unlessgames you had some whishes idea here, if I remember well. Any idea how this could look like?
The text was updated successfully, but these errors were encountered: