sonic-pi-net / sonic-pi

Code. Music. Live.
https://sonic-pi.net
Other
10.75k stars 918 forks source link

Effects around live loops not reacting to changes when re-running a buffer! #794

Open the-drunk-coder opened 8 years ago

the-drunk-coder commented 8 years ago

Affected version: Sonic Pi v2.8.0-dev-a3737, running on Arch Linux (x64)

I can't tell if this is intended behaviour, but when i try to wrap some effects around a _liveloop, change some parameters, and re-run the buffer (that is, without completely stopping in between), the effects aren't reacting to the change.

Example:

with_fx :bitcrusher, bits: 12 do
  live_loop :foo do
    play 70
    sleep 0.5
  end
end

If i want to change the bitrate here, i have to stop the run first (using Stop or Alt-R), then start it again. Otherwise, there'll be no change at all ...

It works fine if the effect is specified within the live loop. It also works using a "normal" loop, instead of a _liveloop.

hzulla commented 8 years ago

This is intended. The definition of the live loop is modified at runtime, so changing an effect only works inside the loop.

xavriley commented 8 years ago

hzulla is right here. For reasons of efficiency with_fx is only evaluated once where possible (i.e. we don't recreate it if it already exists in that thread). There's a little bit in the Tutorial section about loops and fx (6.2 FX in Practice) but maybe we should also write something into the docs for with_fx to make this clearer.

If you want to understand what's happening with the body of live_loop Sam gives quite a good overview of the implementation in his StrangeLoop talk https://youtu.be/YlRTTzlhquo?t=22m36s If you have 10 minutes or so to watch that video it will explain better than I can :)

samaaron commented 8 years ago

It's currently intended, although I spent quite a while last year thinking about how to support the 'obvious' behaviour in a sensible way (I couldn't figure it out).

The real issue is that with_fx affects the current thread and any threads created or code run inside its block. The first time you run a with_fx block containing a live_loop a new thread is created for the live_loop which then works with that FX. However, the second time, no new threads are created within the with_fx block as the live_loop already exists. This means that a new FX is created but is instantly removed as it is affecting no synths or threads.

I'll see if I can find some more time to think about a sensible 'fix' but for now it's intended behaviour.

the-drunk-coder commented 8 years ago

Hmm ok i'd say a good fix for now would be to document it, as from the documentation i wouldn't have guessed this is the intended behaviour at all ... from the user standpoint, it doesn't really feel like creating something new, but rather changing a parameter on an already running effect ...

Staying with the "guitar effects" analogy employed by the documentation, why would i have to stop playing the guitar to turn a knob (assuming i have an expression pedal or agile toes, that is).

But i can also see that it's hard to explain to somebody who doesn't know the internals ...

hzulla commented 8 years ago

Well, the live loop is only replaced with the new code once the old code of the loop has finished its current run. Does that help as an explanation?

the-drunk-coder commented 8 years ago

Well, i more or less understand what's going on, but the explanations have been really technical so far, so they probably wouldn't really help technically less proficient users ...

Plus it's still counter-intuitive ... from what i got so far, it's the only part of Sonic Pi where a parameter change doesn't have an effect when re-running the code.

I mean, explaining this with a simple (hardware-) analogy would be like: "Ok, so you're running your synth through this delay. To change the delay time, please stop your synth, turn off the power, change the parameter, and re-start everything"

I think, as workaround, i can live with using the "loop in a live loop" approach, like:

live_loop :foo do
  with_fx :reverb, mix:0.2 do
    8.times do
      play 70
      sleep 0.5
    end
  end
end

A little less reactive, but ok ...

samaaron commented 8 years ago

The thing to understand is that with_fx always creates a new FX synth. So in your original example, every time you pressed run, you were creating a new :bitcrusher FX and not modifying the old one.

Your 'workaround' is actually the recommended way of working with FX in Sonic Pi and is the best way to live code them. Your pattern is even supported by the reps: opt which removes the need to add an explicit iteration:

live_loop :foo do
  with_fx :reverb, mix:0.2, reps: 8 do
    play 70
    sleep 0.5
  end
end

I'll definitely improve the docs though - thanks for bringing this to my attention :-)