-
Notifications
You must be signed in to change notification settings - Fork 3
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
Aligning events to a grid #36
Comments
Thanks for summarising our discussion Simon. As you know, I began working on this a while back. There are existing functions that need to be examined and subsumed into an overall method to incorporate events from one sc object into another: get-nearest-event (slippery-chicken.lsp) This is also something of interest to Sebastian. I'll scratch my head and get to it asap |
by the way, what would we call this slippery-chicken method? rasterise (usually just images)? magnetise? or just plain old quantise? |
and another: I assume we'd need two sc objects (with one of them just full of rests, perhaps, that the other's events should stick to and replace with pitched events); plus a start and end bar (defaulting to the whole piece), the player mappings (if both sc objects had players with the same name then we could couple these, by default, but what if they don't have the same players and/or the same number of players?) then there's the question of which object gets modified? I'd assume the first one (say, with all the pitches) thus using the timings of the second (say, with nothing but rests in perhaps even a single part, e.g. a bunch of 32nds) then it would also be nice if we could skip providing a 2nd sc object and just give a rhythmic value e.g. 32 so that the first sc would quantise to this rhythm all just thoughts at the mo.: might be worth working this up in a little mind map before implementing |
I think, "quantise" would be a good name, as it will basically be an abstracted version of usual quantisation algoithms, providing "standard functionality" and beyond...
What if the method would not be invoked on the entire sc-object, but on single players, e.g. (method sc-object-A player-A sc-object-B player-B)? One could easily use loop to quantise an entire piece or only certain players parts. That would leave the mapping-issue up to the user.
The first object would be most intuitive to be modified, I suppose.
If the method is given a simple rhythmic value as grid, it should probably also be able to handle a tempo-map and time-signatures? |
that's fine with me
that was my assumption, but as with midi-play etc. the players would default to 'all'
that's the way to go
agreed then
i was thinking that if you only pass a rhythm, then the first thing to do would be to clone the first sc object's bar structure and fill all the bars with that rhythm before then calling the 'main' method i.e. something like this (leaving out keywords for players, start-bar etc.)
|
ps i think if you wanted to fo further and provide a rhythm, a tempo-map, and time-signatures, you'd be opening a smelly can of worms. it'd probably be just easier to create the sc object, perhaps via bars-to-sc...or did you have some cunning data structure in mind to do this more easily? |
Ah, got it! That seems to be an easy way to do "standard quantisation"! Another case: I assume that when two sc-objects (with very different bar-structures) are used, the algorithm would ignore sc1s bar-structure and only look at the absolute start-position of each event and then match the closest event in sc2? In that case, would sc1 always have to be an sc-object (meaning: data that is already in some kind of bar structure) or could it also simply be a list of sc-events, generated "by hand" from any data?
No, not at all. Creating a dummy-sc-object will be the easiest way to handle all applications other than simple quantisation, I guess. |
Exactly: my thought was to offer that but so much more if you pass two sc objects
that's what i was thinking, for sure
Indeed it could be. Good thought.
So actually your proposed method would be the main workhorse, right? Meaning, if passed sc1 then all we'd do is extract a list of events from it, quantise it against sc2, sticking the events into sc2 or a clone thereof.
OK, agreed then. Sounds good. Now it just needs programming :/ |
I made this a while back, it's kinda similar. At least it might help? https://github.com/danieljamesross/slippery-chicken/blob/master/copy-bars-sc2sc.lsp |
Reading back through this thread, why would one need a 2nd sc object? If I wanted to quantise one sc, wouldn't it be more convenient to supply a list of rhythms? Or am I missing something? |
It doesn't quantise though, does it? |
No, I think you're onto something! We could keep both arguments open: either a list or sc object. the method selected will be determined by the argument types but the heavy-lifting will be done by a method (or perhaps now a function) that handles two lists of events, aligning the first with nearest time-neighbours of the second. So we take on essentially the rhythmic character of the 2nd arg, but the pitch character of the 1st. I think we're going to like this. |
One thing that needs to be sorted out though is: what happens if arg1 has loads of events and arg2 not nearly so many. we can always find the nearest by time in arg2, but then that same arg2 event might be the nearest by time to the next several events in arg1. so do we
The results will be quite different. My intuition says go with 2) but should there be an option of using either approach? |
ps i'm off to cut the grass (so fookin' boojwah) and might not be back around these parts for a day or two. keep thinking. this is good |
Might there also be a related method that works with only sounding durations and not written? Like adding "swing" to a midi track as you would in a DAW. E.g. CMN display to be '(q. e) but the CLM/MIDI rendering to be somewhere between that and { 3 tq te } |
My feeling is that would be a different method. We already have the :process-event-fun for cmn-display and :force-velocity for midi-play. We could make that a consistent keyword across all output methods, and even provide a 'humanise' function that varies onset times. But what we're going here is the opposite, no? Want to add an enhancement issue? |
I think it is important to be clear about what the "grid" is (only information to be used, nothing that really sounds in the end) and what the actual musical structure is that should be edited. In that sense, if arg1 (the piece/parts to be edited) would have a lot of events and arg2 (the grid) in the most extreme case only one single event at start-time 0, the result would be one single chord with all the pitches in arg1. That would mean:
Going through arg2s events and replacing the pitches with those of the nearest event in arg1 would make the grid audible in case of standard quantisation, wouldn't it? If one would pass a simple 32 as arg2, the result would be a lot of short notes that change their pitch from time to time. |
Aha, I think we're seeing this quite differently. A chat on Tuesday will help to clarify things. It's important to establish needs before programming this. |
...so here comes the summary (...summery summary..? :D) of our conversation today: The main method that handles the quantisation will receive two lists of events. The first list will be modified and returned. The start-time of each event in ls1 ("musical data") will be replaced with the start-time of the closest event in ls2 ("grid"). The end-time of each event in ls1 will be adjusted accordingly. End-times can optionally also be quantised. Replacing the start-times will be done by a function called once per event in ls1, which can optionally be replaced by a custom function. This way, other data in the events of the first list could as well be manipulated/replaced by data in the second list using this method. |
Hello all, I've worked up a procedure description below and would be curious about your thoughts, above all as to whether you think this will be fit for (your) purpose, but also whether you think it'll work and/or whether I've forgotten or misunderstood something. Best, Michael The events of a slippery-chicken object are quantised to the timing of the As the bar/meter structure of the first slippery-chicken object will adopt the Whilst quantising one list of events to another should present no significant One problem is caused when the nearest event in the 'grid' (second argument) is Another problem is what to do when, even within the time frame of a common bar **** Some assumptions We assume that the start-times of the grid events encompass at least the minimum We also assume that the user will call update-slots on the first A short discussion with Thomas Neuhaus brought into the equation a further **** The methods
|
Blimey, this looks to be a much bigger issue that I had anticipated. I'm still not quite sure what the intention, compositionally speaking, is behind it. @simonbahr would you be able to give me an example of a use case, please? If one is going to the trouble of creating an sc object in the first place, why would it then need quantizing? |
The whole point of this exercise is not just to deplete gray matter but--beyond quantisation methods which could be useful to render e.g. a nested-tuplet heavy piece (rqq-generated perhaps) into simpler rhythms--to approach the idea of mapping one piece onto the structure of another, perhaps by a better composer ;) Imagine your latest highly-complex opus quantised to Eine Kleine Nachtmusik? Who wouldn't want that??? |
most things are, which is why you'll often see me rolling my eyes when people say "couldn't we simply...." |
OK, this now makes sense. So I could generate something super complex and then provide simpler versions depending on the experience of my players. Cool. I suppose it could work in the opposite way, too? |
There's perhaps a case for doing a similar operation with pitches. |
well, that would be handled here actually. your :copy-fun could decide to impose pitches however it wanted |
exactly
i don't see why not though there's probably a limit to the complexity you could generated, especially given that 'stickiness' requires a :shortest-rhythm and I'm not sure right now how we'd avoid that--perhaps someone else has a bright idea |
I think that's acceptable. Do we ever need { 87 : 63 } tuplets? |
Hi Simon, If you have some code for tempo detection in e.g. MIDI files that would be great. Otherwise I'd expect tempi to be passed by the user or simply read from the MIDI file.
How? That's just a list of events, not bars, even though the events could have bar numbers attached to them. (Wasn't this your idea? ;)
From your original comment at the top of this thread: "It would also be usefull [sic] to make events with arbitrary time values displayable in musical notation." So no, it's not necessarily notate-able at all, especially with Thomas's interpolation approach, which I like very much. Let's talk about this tomorrow. In any case I'm all for a sub-project which deals with notating any given time values. It's time we had this. Best, Michael |
Here's the latest refinement. It attempts to choose the least complex rhythm that's within tolerance:
|
Oooh this is a real head scratcher. Nice work! Line 23, we don't need that second Line 24, do we need the lambda if we only call the |
cheeky bugger! ça fait encouler les mouches! mais oui, tu as le droit
we do need the lambda (afaik) to compare just the denominator, not the whole number |
de rien.
I'm overcomplicating it, but is there a way with back commas and (let ((x 3/4)
(y 4/6)
best)
(setq best `(< (denominator ,x) (denominator ,y)))
(eval best)) |
aren't you forgetting the call to sort there?
cheeky bugger! ça fait encouler les mouches! mais oui, tu as le droit
de rien.
I'm overcomplicating it, but is there a way with back commas and eval?
(let ((x 3/4) (y 4/6) best) (setq best `(< (denominator ,x) (denominator ,y))) (eval best))
we do need the lambda (afaik) to compare just the denominator, not the whole number
|
Yes, it's an oversimplification of an overcomplication |
Hah!
|
(sort '(262626/5342 3/4 5/6 7/8 3/5) #'< :key #'denominator) |
I think that's right, and it's even simpler than I had previously thought (if indeed it is correct) |
(defun rationalize-more (float &key (warn t) (tolerance .01)
(num-max 20))
(let* ((r (rationalize float))
(n (numerator r))
(d (denominator r))
(candidates (loop for i from 1 to num-max
for div = (/ d i)
for ni = (round (/ n div))
collect (/ ni i)))
diff tolerated best)
(setq candidates (remove-duplicates candidates))
(multiple-value-bind
(nearest csorted deltas)
(nearest float candidates)
;; now get all those within tolerance and choose the simplest: the one
;; with the lowest denominator
(setq tolerated (loop for d in deltas and cs in csorted
if (< d tolerance) collect cs into result
else do (return result)
finally (return result)) ; just in case they all pass
;; of those within tolerance, prefer the one with the lowest denominator
;; (less complex tuplets)
best (first (sort tolerated #'< :key #'denominator)))
;; believe it or not, we shouldn't need nearest but in case we have no
;; results within tolerance it'll come in handy
(unless best (setq best nearest))
(setq diff (- best float))
(when (and warn (> (abs diff) tolerance))
(warn "rationalize-more:: difference (~a) is > tolerance (~a)"
diff tolerance))
(values best diff)))) |
Now Dan, if you feel like getting your teeth into something proper, there's the matter of splitting arbitrarily timed arbitrarily long events into bars, dividing some of those into two when they tie over bars, generating the data for the rests out of those events, and using rationalise-more to get all the rhythms into bars. no pressure :) |
Nice, yes, that'll do the same job as my call with lambda. I never use |
that was a genuine invitation...not beyond your means ;) |
Oh I took it as such. Gonna have a look tomorrow! |
excellent! we can talk it through perhaps also via signal |
Are you at this already, Dan? I am working on a function for this. Unless you got it already, I will post my attempt here soon and let you both disassemble it (can you say that like this?), ok? |
Arguments:
Return: A list of lists per bar, containing durations of notes (numbers) and rests (numbers in a list) -> '((1/2 (1/4) 1/4) ((1)) (1)), meaning one bar filled with a note by 50%, a rest by 25% and another note by 25%, another bar containing a whole bar rest and a third bar filled with one single note from start to end. Would that be useful? I would leave the "slippery-chicken-side" of it to the grown-ups. ;) |
Thanks Simon, looks good.
It would. And for now I think it's fine to keep this independent of slippery chicken, in order to aid flexibility. On the other hand, we did speak of handling lists of events, which would also be fine, and perhaps convenient because that way you can create rhythm data too, attach tempo changes, meters, etc. etc. But that can also be generated after your method. Just be aware that if you start adding things to your lists that look like slots, then it might be easier to use events--they can always be converted to other object types later, if necessary. |
OK, I guess that's true and I will have to deal with sc-events and bars. ;) That means: There is one problem I don't seem to get around: When rationalizing the events after slicing them into bars, the returned events will not fit into the bars any more. This means, I think, that we can not simply rationalize each event by itself – we rather need another method, "rationalize-bar", that would rationalize the events in a way that they add up to the bar duration exactly, right? Now, that would already be a sort of quantisation (what's the grid?!), and maybe would lead to unexpected results. One simple solution would be to stretch or shorten the last event accordingly, with the side effect, that this event may not have a denominator < 20 anymore. Depending on the quantisation happening afterwards, that would affect the result a lot or not at all. What do you think? |
...an example for the "easy way":
This produces results such as The question is: do we want 61/462 ?! |
Hi Simon, over lunch and after our conversation just now I had some thoughts. If you could get it so far as rationals with a common denominator I'd like to have a poke from there. If that's OK with you. Best, Michael |
Sure! I will finish it to that point and post the code here when I'm done. |
... posting the code does not work properly right now (too many lines, maybe?!) – I'll send you an email... ;) |
Thanks. You can link to code pasted here if you prefer: https://pastebin.com/ Anyway, I've had a poke and found something that seems failsafe in creating rthm-seq-bar objects. I don't think they'll be very nice to read right now, but it's a start. The problem is that all my work at getting nice individual rationals with rationalize-more ends up almost wasted when we combine them: the tuplets get more complicated, as we should expect I suppose. If you don't have the sc regression test stuff you can find what you need here
|
Thank you, Michael! I executed the deftest a couple of times, and every once in a while I get this error:
Do you have an idea why this comes up? |
Yes, that arises when no tuplet is necessary, but I fixed that just after initially posting the code. Apparently 100 tests wasn't enough. When did you copy the code above? If you evaluate what's there now it shouldn't issue this error. |
Ah, I see, it is working now! |
@simonbahr you might be interested to see some of the new files I wrote in import-audio.lsp slippery-chicken/import-audio.lsp Line 165 in d2f7d3f
In particular, event-list-to-bar-list at l.165. It take a list of events of arbitrary length and pushes them into a list of lists, with each sublist adding up to the total time of an arbitrary time sig.
slippery-chicken/import-audio.lsp Line 233 in d2f7d3f
Once I have this list, I use bar-list-to-bars at l. 233 to create actual bars, before passing to bars-to-sc .
|
Thanks, @danieljamesross, these functions may be useful! I will get back to this issue soon and continue to work with Michael on something to make arbitrarily timed events notateable, as Michael told me that such a thing may serve your needs, too. |
It would be very usefull to have a function that can be used to align the time-values of events to a grid. The grid could either be equidistant (piano-roll-style) or could be a complex musical structure itself. This would allow for a "convolution" of events, combining the pitch, etc. values of one group of events with the time values of another group. It would also be usefull to make events with arbitrary time values displayable in musical notation.
The text was updated successfully, but these errors were encountered: