Closed gogins closed 1 year ago
Haps for csoundn
don't need to be notes, and if they are notes, they aren't the right pitches.
I'm trying haps that aren't notes, but when I enqueue them for pianoroll
, I will make them notes.
But then I have made the gain 0, which turns the piano roll off. I will put the gain back in when I enqueue the hap.
The sad story is that pitch is sometimes MIDI key, sometimes frequency; and also sometimes hap.value
, sometimes hap.value.value
, and sometimes hap.value.note
.
I suspect that note
is assumed to trigger once and only once in a functional composition.
Cases:
[query]: valueToMidi: expected freq or note to be set
. This is because piano
gets an Object with .value.note
where note
is a MIDI key.[query]: valueToMidi: expected freq or note to be set
. Ditto.This is truly maddening. I must try again to get the csoundn
output triggered in exactly the same way as piano
.
Progress? Who knows? Now, I'm getting all the csoundn
notes piled up at the start of the cycle, and I'm also still getting the default sound at the correct times and pitches. Part of the piano roll looks correct.
I have silenced the default output, and put the gain back into the Haps for pianoroll
. Things are a little better, but:
There is an improvement:
typeof hap.value === number
.globalThis.__cyclist__
.csoundn
, to adjust p2
before creating i
statements.p2
.Then it seems to work, although I have more to do in order to prove that. At any rate, having gotten this far seems to prove that my objective is possible after all. The key points:
csoundn
to a global buffer that feeds pianoroll
along with the Haps from the default outputs.This is a hack and thus most likely is fragile, but I never thought I could get this to work at all.
If it really does work, I can prove that by creating some special logging and switching back and forth between piano
and csoundn
.
Possible better design: a new Pattern that inserts a csoundn
callback in Hap.context
.
Probably what needs to happen is a way to schedule a WebAudio node that does nothing but call an alternative output such as csoundn
. Does WebAudio provide for that?
Reduce patches of Strudel to a minimum. I think I have done that. It's still a hack. I need to talk to @felixroos.
you've summoned me through inserting my name.. i don't fully understand this thread, could you maybe try to condense it to something i can respond to?
Yes indeed, I will try to condense this to something we can talk about without misunderstanding each other. I will do that soon, no later than a few days -- I'll insert your name again, but not before I'm ready. I find this stuff complex and subtle.
Thanks, Mike
Michael Gogins Irreducible Productions http://michaelgogins.tumblr.com Michael dot Gogins at gmail dot com
On Wed, Sep 27, 2023 at 3:44 PM Felix Roos @.***> wrote:
Reduce patches of Strudel to a minimum. I think I have done that. It's still a hack. I need to talk to @felixroos https://github.com/felixroos.
you've summoned me through inserting my name.. i don't fully understand this thread, could you maybe try to condense it to something i can respond to?
— Reply to this email directly, view it on GitHub https://github.com/gogins/cloud-5/issues/36#issuecomment-1737978371, or unsubscribe https://github.com/notifications/unsubscribe-auth/ABQIGJIS5FZ2AVXZ7IGFMFLX4R63PANCNFSM6AAAAAA5FF5I2I . You are receiving this because you were assigned.Message ID: @.***>
Okay, @felixroos, here's the summary.
cloud-5 is my project to create a truly powerful browser-based system, or maybe toolkit is a better word, for doing computer music. It includes (of course) all of HTML5, the WebAssembly build of Csound, the WebAssembly build of CsoundAC's algorithmic composition classes, and Strudel (as a component, not as a front end).
cloud-music is my Web site for publishing compositions created with cloud-5. Not all of them use Strudel.
In order to use Strudel as a component, I had to hack the Strudel REPL and make it into a custom element that runs Strudel in an iframe, with a simple API for starting and stopping the Strudel patch from JavaScript code. An example is here.
In order to use CsoundAC's object-oriented facilities in Strudel, I defined a StatefulPatterns class in JavaScript. This has a function to be called in the constructor that registers every class member function as a Pattern of the same name. The member functions have the same parameters as the Pattern function. Of course a class derived from StatefulPatterns can hold any kind of state. Each such member function implements both a trigger and a regular value. An example is here. The CsoundAC code is used to define some ChordPatterns and ScalePatterns in csoundac.mjs.
In order for states not to mess with patterns and vice versa, I defined a new Csound output, csoundn
, in csoundac.mjs
. csoundn
does not use triggers. Instead, it uses the default note
output, which it silences. I do this because, as I understand it, the one place where state and pure functional queries are guaranteed to coincide is in the scheduling of the notes in the WebAudio system. In order to accurately schedule notes in the middle of cycles, I provided csoundn
with access to the Cyclist by creating a singleton globalThis.__cyclist__
.
In order for a generative stateful Pattern like Logistic to produce notes that behave properly in pianoroll
, I defined a global buffer for Haps that are consumed by pianoroll
. Each Hap that actually produces a Csound note, is pushed onto this global buffer. Then, pianoroll
merges the Haps it receives from csound
with its existing haps
parameter, which is crammed with redundant Haps produced by queries to the stateful Patterns. The haps
parameter is then filtered as usual.
The patches to Strudel that help to do these things are here: patches.txt. This is all too hackish but it is definitely starting to do what I want.
Comments and suggestions are welcome.
For one hour I've tried to understand why you need the StatefulPatterns class.. couldn't you just use register directly:
https://strudel.tidalcycles.org/?VEBHQOsi7XRK
Another, imo more direct way to handle mutable values is using the ref function:
https://strudel.tidalcycles.org/?YH0wVNwQ_Fmi
Wouldn't that simplify things alot? I have yet to understand what you mean by
the one place where state and pure functional queries are guaranteed to coincide is in the scheduling of the notes in the WebAudio system
...to help me test these things better, it is always helpful if you provide a link to the strudel repl. It takes a lot of time for me to clone your repo, set everything up and test it
another thing worth mentioning is that the state update can also happen disconnected from your target:
https://strudel.tidalcycles.org/?OwGpj08glBu0
here, the tick function will only run every 4 cycles. The early makes sure the state is already set before the notes are queried
For one hour I've tried to understand why you need the StatefulPatterns class.. couldn't you just use register directly:
Of course, using register
directly works. However, when a number of Patterns that share the same state need to be defined, StatefulPatterns greatly simplifies the code and makes it much easier to read and maintain. That's the case for the ChordPatterns and ScalePatterns classes in csoundac.mjs.
...to help me test these things better, it is always helpful if you provide a link to the strudel repl. It takes a lot of time for me to clone your repo, set everything up and test it
I do not maintain a fork or branch of the Strudel repository. I started out by doing that, but what I am doing now is considerably less work, and it keeps me closer to the released Strudel code. If somebody wants to see my version of Strudel in action, they need to build my repository.
What I would greatly prefer is if I did not need to make any changes to Strudel at all, in order to be able to use Strudel as a component in cloud-5.
To this end, after I have got cloud-5 in a stable form, and have reduced to an absolute minimum the patches I need, I plan to submit some pull requests to the Strudel repository.
For one hour I've tried to understand why you need the StatefulPatterns class.. couldn't you just use register directly:
This like the examples I looked at in order to develop the StatefulPatterns class, so of course I know it is possible to use register
directly. However, I found that if I have some state that will be shared by several Patterns, the StatefulPatterns class greatly simplifies the code and makes it much easier to read and use.
Another, imo more direct way to handle mutable values is using the ref function:
https://strudel.tidalcycles.org/?YH0wVNwQ_Fmi
Wouldn't that simplify things alot?
Thanks, that is very interesting. I had not considered using ref
. I will make a branch in my code to see if I can use ref
to simplify things and maybe even reduce the number of patches I make.
I created a test piece using ref
with an instance of a class. I was able to delete my hacks for pianoroll
and the piano roll still displayed correctly, so I can take the pianoroll
patches out of my code.
I would now like to use ref
or something like it in the definition of StatefulPatterns. Instead of merely returning a value from the state, I would like to replace the existing state, or even do some calculations based on the state before returning the value (as in ChordPatterns.acCV
which conforms notes to a chord).
Wrong, I was too hasty, the piano roll did not display correctly. The whole piano roll display jumps up or down with each application of ref
.
I think I need to write a variant of this that accepts parameters. Does that make sense?
/**
* Returns a new pattern, with the function applied to the value of
* each hap. It has the alias {@link Pattern#fmap}.
* @synonyms fmap
* @param {Function} func to to apply to the value
* @returns Pattern
* @example
* "0 1 2".withValue(v => v + 10).log()
*/
withValue(func) {
return new Pattern((state) => this.query(state).map((hap) => hap.withValue(func)));
}
I think I need to write a variant of this that accepts parameters. Does that make sense?
/** * Returns a new pattern, with the function applied to the value of * each hap. It has the alias {@link Pattern#fmap}. * @synonyms fmap * @param {Function} func to to apply to the value * @returns Pattern * @example * "0 1 2".withValue(v => v + 10).log() */ withValue(func) { return new Pattern((state) => this.query(state).map((hap) => hap.withValue(func))); }
What parameters? The above seems unmodified
Closing this for now, see #42.
I still have not got the stateful patterns correct. In http://localhost:8000/csoundac_example_03_acCT.html if the output is piano the music sounds correct, but the pianoroll is flattened and jumping. If the output is csoundn, the pianoroll shows up, but it only shows the haps from Logistic, and that is what csoundn plays, ignoring all the chord stuff that sounded like it was working with piano.