Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
    Tylor Steinberger
    @TylorS
    All of the other internals of @most/core always avoid calling sink.event in the same call stack, there's some docs about that here - https://mostcore.readthedocs.io/en/latest/concepts.html#always-async
    There's some combinators exposed by @most/scheduler which can help you to schedule tasks to be run, and then there's a couple of helpers in @most/core for the kinds of tasks it internally uses
    Once you have a Task and a Scheduler you'll be able to use the Scheduler APIs https://mostcore.readthedocs.io/en/latest/api.html#scheduling-tasks
    Tylor Steinberger
    @TylorS
    That might look something like this
    import { delay } from '@most/scheduler'
    import { propagateEventTask } from '@most/core'
    import { disposeAll } from '@most/disposable'
    
    const queuePulses = numPulses => ({
          run: (sink, scheduler) => {
            const times = Array.from({ length: numPulses }, (_, t) => t)
            const tasks = times.map(t => delay(t, propagateEventTask(null, sink), scheduler))
    
            return disposeAll(tasks)
          }
        })
    This is effectively a multi-value variant of our current at factory function
    Tylor Steinberger
    @TylorS
    Since this is the case you could rewrite this using standard combinators something like
    import {mergeArray, at } from '@most/core'
    
    const queuePulses = numPulses => mergeArray(Array.from({ length: numPulses }, (_, t) => at(t, null)))
    This code works a bit better than the code above since it properly handles ending the stream when all the pulses complete
    Tylor Steinberger
    @TylorS
    Another way of representing this kind of stream
    const  queuePulses = numPulses => take(numPulses, constant(null, periodic(1)))
    Tylor Steinberger
    @TylorS
    I tried to hack up an attempt at creating a "multiplying" scheduler here to play around with this ideas, maybe it's useful maybe its not :smile: https://codesandbox.io/s/smoosh-water-3km5h?file=/src/index.ts
    Tylor Steinberger
    @TylorS
    I think ultimately it may make sense for there to be a single scheduler, but as you are constructing other time-altering schedulers they will form a tree with a single root which is the source of "real life" time that other nodes in the tree will distort locally using some kind math against time
    Antti Niemenpää
    @niant

    Hey, I'm having hard time to figure out the run functionality or rather the Disposable it provides. Could someone explain why does this throw an error or how should it work?

    const emptySink = {
      event: () => {},
      error: () => {},
      end: () => {},
    };
    const { dispose } = run(emptySink, newDefaultScheduler(), now(true));
    dispose();

    It'll throw Uncaught TypeError: Cannot set property 'active' of undefined

    Tylor Steinberger
    @TylorS
    Hey @niant that almost seems like a bug. dispose() really shouldn't ever throw
    I'll try to take a better look soon
    Tylor Steinberger
    @TylorS
    If you change the last 2 lines to this it will work
    const disposable = run(emptySink, newDefaultScheduler(), now(true));
    disposable.dispose()
    Would you mind creating an issue to track this? I tried making it a fat arrow function locally, but it seems like the way it is being compiled still causes things to break. We need to do some build updates anyways, and we can try to bind dispose
    I much prefer the syntax that you are using
    Antti Niemenpää
    @niant
    Ah, thank you! It seems, I've been doing only functional programming for far too long 🙈I guess it works as it should unless it's changed fundamentally a bit 🤔
    Tylor Steinberger
    @TylorS
    Yeah.. most only uses classes for the performance characteristics. It's not often I have to think about this
    What kind of stuff are you using most for?
    Antti Niemenpää
    @niant
    I'm using it for a GPS navigation/communication software or rather migrating to use most 🙂
    Micah
    @micahscopes

    Hey @TylorS, that was some helpful perspective, thank you. The multiplying scheduler is great, I also made something like this myself, but wasn't satisfied with it because I wanted to be able to modify the current rate of playback at any given moment, on demand.

    And yeah, I ended up figuring out that it's not really possible to "warp" time over an existing stream's scheduled events in a way analogous to how map transforms an existing stream's values. The scheduled events are stored in a Timeline object I believe (? can't recall at the moment as it's been a whole week since I was in there!) ... so in order to manipulate a stream's pending events it'd be necessary to unschedule and reschedule them. I'm not so sure it's worthwhile to do that given the current architecture.

    For my actual project I've been thinking that instead I'll just use lodash to manipulate events (e.g. standard MIDI file data) before passing them into a kind of stream generator. While I'd love to be able to take a stream with pending events and generate a new stream with time-transformed pending events, right now it doesn't seem like most/core is really designed to make that sort of thing very easy.

    As far as scheduling is concerned, I checked out your virtual clock/scheduling stuff and it seems like it could be great for a variable speed scheduler. In particular, standard MIDI files encode event time in units of "ticks", which are fractions of a beat, usually around 1/96th of a beat or something. I'm thinking of forking your virtual scheduler and adding some features for doing realtime playback at an adjustable rate. At a glance it seems perfect for processing MIDI events at a variable rate. I can just schedule the events in abstract metronome time and use requestAnimationFrame or something to process each tick's worth of events, based on the current tempo and the amount of wall clock time that has passed since the last tick.

    c.c. @briancavalier you might be interested in this failed experiment too. ^^^ ... I tried to create a warp function analogous to map that would let you take a stream with pending events and generate a new stream with time-transformed pending events. But using sink.event like that doesn't actually do what I'd hoped in the slightest :sweat_smile:
    Micah
    @micahscopes
    (most of the complexity of that experiment was in fusing all the combinations of filter, map and warp)
    Brian Cavalier
    @briancavalier
    @micahscopes Thanks for the link. I'll take a look soon.
    The sink API is simply a way to inform a sink that an event happened at a particular time. Actually, scheduling events is the responsibility of a scheduler.
    Micah
    @micahscopes
    This makes sense now! The experiment was a failure but I still like the high level "warp" API attempted there. If it was possible to implement an API like that for streams with prescheduled/preschedulable events, I'd be interested in understanding how. Seems like you'd need to get a Stream's Scheduler from that stream and somehow create a duplicate of the Scheduler itself with just the re-scheduled (and transformed) events just from that stream. Sounds complicated but maybe it wouldn't be too hard to do.
    Antti Niemenpää
    @niant
    I have a question about the sample and the adapter. If I call adapter right after runEffects it seems to never trigger the dependent sample stream, unless I put it around some async operation (setTimeout).. Does someone know why so? I have some theories, but no idea if they are correct.
    const x$ = now('x');
    const [setY, y$] = createAdapter();
    
    const test$ = tap(
      (x) => {
        console.log('never here', x);
      },
      sample(x$, y$)
    );
    
    runEffects(test$, newDefaultScheduler());
    
    setY('y'); // never triggers sample stream
    
    // This async would work
    // setTimeout(() => setY('y'))
    Tylor Steinberger
    @TylorS
    @micahscopes It's not as interesting as you're looking to do, but our mergeMapCorruently/chain implementation uses relative schedulers when creating new streams to augment the Scheduler instance, and may be of some interest to you - https://github.com/mostjs/core/blob/master/packages/core/src/combinator/mergeConcurrently.ts
    Tylor Steinberger
    @TylorS

    @niant You're running into the impedance mismatch of @most/adapter and the rest of @most/core. @most/core will always start emitting events in the next tick of the event loop. Here are the associated docs. It helps avoid race conditions within the library and will allow a Stream to synchronously return a Disposable instance before emitting any events so they can always be canceled. @most/adapter does not follow this rule, and it is up to the user to use it "appropriately" for the sake of adapting to external libraries that don't lend themselves to a more declarative approach that most favors.

    So what's actually happening? When you call setY synchronously, no other x$ values have occurred so there are no events to sample from. When you call setY asynchronously, the scheduled x value will have had the time to asynchronously emit and calling setY does have a value to sample from.

    Antti Niemenpää
    @niant
    Thanks @TylorS for the explanation! I read the "always async" part from the documentation before and that's why I got confused because it seemed it didn't apply in this example. But since adapter does not follow that rule, it explains it perfectly.
    1 reply
    Micah
    @micahscopes
    @TylorS I hope you'll find it flattering and won't be offended that I basically just stole your "most-virtual-scheduler" code and changed all the abstractions to make a media oriented scheduler :sweat_smile: ... it's basically perfect for what I need. I'm planning on adding some more features, but for now I just renamed everything :grimacing:
    Micah
    @micahscopes
    Also planning on adding some Web Audio based scheduling tools to progress through the scheduler at a dynamic, precise rate
    gonna use it with this: https://www.npmjs.com/package/wa-metro
    (Where the metronome callback will just pulse the timer by 1 "tick" at a time. Standard midi files usually schedule events at something like 96 ticks/beat)
    Tylor Steinberger
    @TylorS
    Definitely not offended, OSS is meant to be shared and reused IMO :smile:
    I'm glad you were able to make some progress on what you need
    Micah
    @micahscopes
    @TylorS I tried it out and it worked great! Except for the timing issues caused by doing timing in the main thread where all the heavy react stuff and game animation were also being computed. So my next step is to attempt using the @most stuff in an audioworklet processor... stay tuned, I'll share more when I've made some progress. I'm really pumped to be able to do generative music stuff with @most and lodash or whatever, it's a long time dream come true
    If I'm successful, it seems like it'd be useful to share any extra utilities I make for doing that in my little fork
    Micah
    @micahscopes
    I have a periodic stream and I'd like to make a new periodic stream derived from it that only includes every Nth event. How can I do this?
    Tylor Steinberger
    @TylorS
    Hey @micahscopes you might be able to do what you need using loop and switchLatest/chain/mergeConcurrently depending on the concurrency you need. Something like this to do the nth of a Stream might help
    const nth = <A>(n: number, stream: Stream<A>) => 
      switchLatest(loop(
        (acc, a) => acc === n  
          ? { seed: 0, value: now(a) } 
          : { seed: acc + 1, value: empty() }, 
        0, 
        stream
      ))
    Tylor Steinberger
    @TylorS
    From there you should be able to construct a higher-order stream for the periodic portions
    const stream = chain(
      () => nth(n2, periodic(n3)),
      periodic(n1)
    )
    Micah
    @micahscopes
    awesome, thanks @TylorS
    Micah
    @micahscopes
    by the way, I got my scheduler working inside of an audioworklet processor, sending MIDI events to another audioworklet processor! it's awesome
    Micah
    @micahscopes
    just wanted to share that I've pretty much finished prototyping using @most/core inside of an AudioWorkletProcessor to load, generate, schedule and transform MIDI events that get played by this synth
    I'm planning on releasing this stuff as a little toolkit once I get a chance to clean it up and organize it!