gogins / cloud-5

Complete browser-based computer music studio for algorithmic composition and live coding
Other
9 stars 1 forks source link

Inconsistent representation of pitch may break CsoundAC's stateful patterns #36

Closed gogins closed 1 year ago

gogins commented 1 year ago

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.

gogins commented 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.

gogins commented 1 year ago

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.

gogins commented 1 year ago

Cases:

gogins commented 1 year ago

This is truly maddening. I must try again to get the csoundn output triggered in exactly the same way as piano.

gogins commented 1 year ago

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.

gogins commented 1 year ago

I have silenced the default output, and put the gain back into the Haps for pianoroll. Things are a little better, but:

gogins commented 1 year ago

There is an improvement:

  1. Stateful generators must use bare values, i.e. typeof hap.value === number.
  2. Expose Cyclist singleton as globalThis.__cyclist__.
  3. Discard notes (Haps) that are not onsets.
  4. Crude imitation of the scheduling that Cyclist seems to use for firing triggers, in csoundn, to adjust p2 before creating i statements.
  5. Discard notes that end up with negative 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:

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.

gogins commented 1 year ago

Possible better design: a new Pattern that inserts a csoundn callback in Hap.context.

gogins commented 1 year ago

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?

felixroos commented 1 year ago

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?

gogins commented 1 year ago

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: @.***>

gogins commented 1 year ago

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.

felixroos commented 1 year ago

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

felixroos commented 1 year ago

...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

felixroos commented 1 year ago

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

gogins commented 1 year ago

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

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.

gogins commented 1 year ago

...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.

gogins commented 1 year ago

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

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.

gogins commented 1 year ago

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.

gogins commented 1 year ago

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)));
  }
felixroos commented 1 year ago

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

gogins commented 1 year ago

Closing this for now, see #42.