Skip to content
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

Extend pattern language #375

Open
wants to merge 5 commits into
base: master
Choose a base branch
from

Conversation

pdcawley
Copy link
Contributor

The commit messages have most of what this is about. Basically it's to enabled dotted notes and shortened notes within the pattern language. For an example of what sort of thing it enables, try the following:

(:| pat-1 96 0 (play syn1 @1 (cosr (cosr 75 2 1/2) 1 1) (* 0.95 dur)) (g3 + a3 - b3 b3 c4 b3 a3 c4 b3 a3 g3 f#3 e3 | d3 |
g3 + a3 - b3 b3 c4 b3 a3 c4 b3 g3 g3 f#3 g3 | g3 |
d4 (c4 b3) a3 (a3 b3) c4 (b3 a3) g3 b3 a3 g3 f#3 g3 a3 | a3 |
d4 (c4 b3) a3 (a3 b3) c4 (b3 a3) g3 b3 a3 g3 g3 f#3 g3 | g3 |
bb3 (a3 g3) bb3 (a3 g3) f#3 g3 a3 | d3 e3 f#3 g3 a3 bb3 a3 g3
bb3 (a3 g3) bb3 (a3 g3) f#3 g3 a3 | d3 e3 f#3 g3 g3 f#3 g3 |))`

with a 180 bpm tempo

I find it handy to have a shorthand notes based on the current key/scale, but
that might change over the course of a pattern, and it's also good to be able
to specify accidentals that override the current scale, so simply doing `(qnt
@1)` in the the note function of the looper doesn't work as well as one might
hope.

So... I've tweaked rmap_helper to check if `(eval l)` returns a procedure and,
if so to call that proc. Thinking about this, it should be possible just do
`(if (procedure @1) (@1) @1)` in the player function.

However, one thing that is definitely necessary to make this work is to change
`(eval l)` to `(eval (interaction-environment))`, otherwise it tries to
evaluate the wrong `f`
A common pattern in traditional tunes is the 𝅘𝅥𝅭 𝅘𝅥𝅮 rhythm, which proved to be
annoyingly hard to express in the ':>' pattern language. You basically have to
replace every note with 'note |', and then do 'dotted-crotchet | | quaver'
which is somewhat annoying.

So, I extended the language to make `note + note -` do the trick (it too me
a while to spot that scheme was never going to let me do `note . note -`.

I've also factored out the code that maps from a note symbol to a number and
which works out where the actual notes are in a pattern and calculates their
positions (both in time and within the pattern list). In theory, this should
allow for the pattern interpretation part of the pattern player to be
parameterised, but I've not really started on that work yet.
This lets us express the common 3 notes in the space of 2 triplet by doing
something like:

    (60 62 63) |
@benswift
Copy link
Collaborator

Hey, thanks for the contribution - things are a bit hectic here rn but I'll look at it asap.

@pdcawley
Copy link
Contributor Author

No rush. Hope the jet lag isn’t hammering you too hard

@benswift
Copy link
Collaborator

benswift commented Mar 8, 2020

yep, soz - still on the todo list (and climbing)

@benswift
Copy link
Collaborator

hey @pdcawley I've had a chance to have a look. cool! nice work getting it going, as you (now) know the guts of the pattern language stuff could do with some re-factoring.

My thoughts on this PR at the moment are:

  1. I like the idea of having multiple "pluggable" pattern languages, so there'd be the default one, your abc one, etc.

  2. we need a more disciplined way of defining the "special bindings" for use within the pattern languages (e.g. defining global functions for each note name as in this PR is just going to trip folks up who accidentally shadow/re-define those things, and then

  3. I personally don't love the fact that symbols in the pattern list are 'eagerly' evaluated (so that e.g. you can't currently pass in symbols like i or v7 for use in chord functions through the pattern list), so it's worth thinking about how we want this to work as well (which relates to 2.)

Overall, I think the best way forward is

  1. define a nicer "base representation" for a pattern, including full info about the values in the pattern list and the time that they should be eval'ed (can/should be verbose, not meant to be written by humans)

  2. every different pattern language is therefore just a mapping from pattern->"base representation" (which includes local bindings for any relevant symbols, e.g. a, b, g)

  3. there's a nice method for creating a new pattern language runner (like :>) from (2), and perhaps we could pre-bind a few by default (e.g. :> is the original, :abc> is the abc one, etc).

I think that's a more principled way to go forward. So I think I won't merge this just yet. I don't suppose you'd be interested in helping out with some of the above work? Obviously it's open source, so I have no coercive power, but the more the merrier with implementation, and I'm happy to help out as well.

Thoughts?

@benswift
Copy link
Collaborator

One more thing, in terms of not breaking the existing stuff, not many folks are using this at present, so as long as the examples in examples/sharedsystem all stay working then I think we're all good (and we can even modify them if necessary).

@pdcawley
Copy link
Contributor Author

That sounds like a way forward, certainly; I think it should be possible to do a lot of the symbol interpretation by having a per-interpreter environment that can be passed as the second argument to eval, but I'm not entirely sure where that needs to happen. If it's not done pretty eagerly, it's hard to get the duration modifiers to work. Maybe that aspect of any pattern language needs to be eagerly interpreted, and have everything else be evaluated as late as possible.

I have time on my hands with the lock down, so I think I'll start by trying to encapsulate an :abc> interpreter, and break it down into a macro-like step that fiddles with an entire pattern and an environment that means the looper will do something like (eval play-form interpreter-env) for each event in the pattern.

@benswift
Copy link
Collaborator

Hey @pdcawley that sounds like a solid approach. The other idea I'd had (a bit less clean than using different environments) was basically to have the pattern eval machinery macroexpand a big (let ((c4 60)) ... with all the necessary let-bindings around each invocation of the pattern expression; so that each new "mini language" defines both a list of bindings (to be provided to the aforementioned let and a mapping function to map the input pattern to the internal "base representation").

But I must confess I haven't thought about it deeply, so whichever approach seems best would be welcome. The main criteria is that the "original" language still works, and (ideally) that the internals of the pattern language stuff are a bit more modular and easier to reason about and modify. So I think you're well placed to help with that :)

Re: the early/late eval, my preference is also for it to be late wherever possible, but in some cases it's going to have to be eager, and I think that's ok as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants