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

let's sort out the panning scheme #36

Open
kindohm opened this issue Dec 20, 2016 · 81 comments
Open

let's sort out the panning scheme #36

kindohm opened this issue Dec 20, 2016 · 81 comments

Comments

@kindohm
Copy link

kindohm commented Dec 20, 2016

There "stereo image" of a sample is lost when played in SuperDirt. For example, a sample that inherently contains right and left panning sounds like a "mono" sample without any panning in SuperDirt. Example:

https://soundcloud.com/kindohm/stereo-image-difference/s-Oh8f6

The above is a recording of the sample played in SuperDirt, then in Classic Dirt. The sample has a panning oscillation that should be audible in any audio player. In SuperDirt, it sounds like a "mono" sample that doesn't pan. In classic Dirt, the panning is audible.

@telephon
Copy link
Contributor

telephon commented Dec 20, 2016

Ah that is unintended, it must have been a misunderstanding from past conversations. You can easily play around with different channel mappings as you go:


(
DirtPan.defaultMixingFunction = #{ |channels|
	channels.flop.collect { |ch, i| ch[i] ?? { DC.ar(0) } }
};
~dirt.loadSynthDefs;
)

(
DirtPan.defaultMixingFunction = #{ |channels|
	var which = MouseX.kr(0, 1);
	var first, second;
	first = channels.first.size.collect({ |i| channels @@ i @ i });
	second = channels.collect({ |x, i| x.rotate(i.neg) });
	LinXFade2.ar(first, second, which * 2 - 1)
	//first;
	//channels @@ 0
};
~dirt.loadSynthDefs;
)

// back to "normal"
(
DirtPan.defaultMixingFunction = #{ |channels|
	channels.postcs;
	channels.sum
};
~dirt.loadSynthDefs;
)

there are a lot of possible solutions, maybe we find an optimal default.

@bgold-cosmos
Copy link
Contributor

bgold-cosmos commented Dec 20, 2016

I believe this is a result of Classic Dirt's behavior being a little confusing, and due to "panning" having an ambiguous definition for stereo signals

IIRC, Classic Dirt will play stereo samples in stereo, but only if pan is unspecified (or maybe also if it's exactly 0.5). Any other pan value will cause it to mix down to mono, then pan.

I believe SuperDirt, by default, will just mix down to mono then pan with any input. If you want something else, like @telephon says there are a number of possible options. In my own SuperDirt what I did was to insert something like this into the dirt_sample_%_% SynthDef in core-synths.scd:

			if (sound.numChannels == 1) {
				sound = DirtPan.ar(sound, numChannels, pan);
			} {
				sound = Balance2.ar(sound[0], sound[1], pan*2-1);
				sound = DirtPan.ar(sound, numChannels, 0.5, 1, {|x| x});
			};

Basically, interpret pan as a balance for stereo samples and preserve that by using the custom mixing function {|x|, x} as an argument to DirtPan. This is still different from Classic Dirt's behavior, but it works for me :)

@yaxu
Copy link
Collaborator

yaxu commented Dec 20, 2016

Yes I think there would be a better default to the one that classic dirt has. Personally though I'd want 0 to be hard left and 1 to be hard right, and stereo image to only be clearly preserved around the centre. Does that make sense for others?

@telephon
Copy link
Contributor

yes - how would it translate to multichannel output / multichannel in + multichannel output ?

@yaxu
Copy link
Collaborator

yaxu commented Dec 20, 2016

I can't see how this generalises to multichannel outputs or soundfiles.. 2 in/out seems a special case. But maybe we can make it generalise with an additional parameter, panspread and/or panmode to control the behaviour?

On a tangent, it'd be nice if a ring configuration went from -1 to 1, and if this worked for stereo as well, so -1 to 1 went from right to left, and then back again. Then jux would work for both stereo and multichannel. I think we discussed this before.

@telephon
Copy link
Contributor

On a tangent, it'd be nice if a ring configuration went from -1 to 1, and if this worked for stereo as well, so -1 to 1 went from right to left, and then back again. Then jux would work for both stereo and multichannel. I think we discussed this before.

Currently, both stereo and multichannel panning works between 0 and 1, in stereo it is a left -> right pan, in multichannel it is a full round. How should it be mapped? Do you mean we should interpret -1 for stereo as 1?

@kindohm
Copy link
Author

kindohm commented Dec 22, 2016

I really can't speak to scenarios where channels > 2. In the case of a 2-channel (L and R) setup, I think pan normally just decreases amplitude on side opposite to the pan. If pan ranges from 0..1:

pan "0" : 100% left, 0% right
pan "0.25" : 100% left, 50% right
pan "0.75" : 50% left, 100% right

This will work for any min/max/range of pan, whether it's 0..1 or -1..1.

But like I said, I really have no idea how this translates to a setup with more than two channels.

@telephon
Copy link
Contributor

Let's say you have a stereo sound file with a sawtooth wave on the left and a sine on the right. Hen panning it, would the sine ever be audible on the left? And the sawtooth on the right?

@kindohm
Copy link
Author

kindohm commented Dec 22, 2016

@telephon no. In a two-channel setup, Pan is the expression of the amplitude of either side; it will never blend the two channels. I've never used any audio editing/recording/mixing software that does something different with "panning".

Again, let's say pan can range from 0..1

pan "0" = sawtooth (left channel) 100% amplitude, sine (right channel) 0% amplitude
pan "0.125" = sawtooth 100%, sine 25%
pan "0.25" = sawtooth 100%, sine 50%
pan "0.5" = sawtooth 100%, sine 100%
pan "0.75" = sawtooth 50%, sine 100%
pan "0.825" = sawtooth 25%, sine 100%
pan "1" = sawtooth 0%, sine 100%

@telephon
Copy link
Contributor

well, in the standard mixing, I would have thought that e.g. with pan 0.25, you'd have sawtooth 75 % and sine 25 %?

@telephon
Copy link
Contributor

(Btw. the reason we are discussing here is that we try to find a solution that is more general than the conventions – that naturally involves trade offs.)

@kindohm
Copy link
Author

kindohm commented Dec 22, 2016

@telephon that is not my understanding or expectation of how pan would work. I would expect when panning to one side that the amplitude remains at 100% on that side. I just confirmed this behavior on both my physical mixer and the mixer in my audio software.

No problem with the discussion; it's good to figure things out :) I am arguing strongly for convention in this case. If there is a more general problem to solve that would benefit from a non-conventional approach, then so be it, but I think it would go against what most Tidal users would expect.

My personal preference would be a conventional panning implementation by default, and perhaps an option for a less-conventional, blended panning implementation when more than two channels are used. My opinion is that this is what most new Tidal users would expect.

@telephon
Copy link
Contributor

@kindohm yes, that would be great if convention turns out to be a special case of a more general scheme. Right now I suspect that this is difficult, the possibilities branch out a bit too much.

I didn't know that on mixers the panning scheme is "hemispherical". It's a good convention, but at least one problem seems to me that it has no meaning outside stereo, has it?

I'm currently rewriting DirtPan so that you can fully define your own defaults. Still, it would be nice if we had a list of named schemes.

@bgold-cosmos
Copy link
Contributor

I suspect the main combinations people use in practice are these:

  • one-channel input, two-channel output: pan works as everyone would expect
  • two-channel input, two-channel output: most people probably expect a "balance" behavior as @kindohm describes, which is really not a specific case of anything more general
  • one-channel input, multi-channel output: pan generalizes naturally
  • two-channel input, multi-channel output: ??? but I'm guessing the most common expectation is to treat the inputs as essentially two one-channel signals and move them around in the multichannel space as pan normally does with such things. This is not a generalization of the 2->2 case.

I don't know if people work with multichannel inputs -> multichannel out, but I've never run into it.

@telephon
Copy link
Contributor

@bgold-cosmos yes, that seems to be about what I have come up in my notes on paper. Although for the 2-2 case I see that sometimes you really want to only have the left channel when you pan to 0. So already here there are are four possibilities: mix+pan, balance, hemispherical and caterpillar like, as discussed above.

I don't know if people work with multichannel inputs -> multichannel out, but I've never run into it.

It is done a lot in electroacoustic music, and I think that tidal is a valuable language in these areas. In any case it is quite idiomatic in supercollider to think in terms of multiple channels mapped in various ways.

@telephon
Copy link
Contributor

@muellmusik how would you do it?

@muellmusik
Copy link

Um, I'm not sure exactly what 'it' is here, but a couple of observations:

  • Balance and pan are more or less the same thing if your two channels are correlated. This will be the case if you've equal power panned a mono signal.
  • There is such a thing as stereo panning. Of course, on most analog mixers that don't have stereo channels, you'd get this in the way you'd expect, i.e. a separate pan for L and R as you have to route them separately. But I've seen 'single pot' stereo panners as well. They're not quite as good though, since you can't bring both channels in and narrow the field. (I don't often want things more mono, but hey..)
  • Splay and SplayAz from SC seem quite generalisable to different numbers of channels. Equal power pan across some contiguous range of speakers. Wide splay of a stereo signal to two channels is hard assign.

@telephon
Copy link
Contributor

@muellmusic: thanks for chiming in (pun intended). The meaning of 'it' is: to find a good configurable default of how to pan an N-channel input to an M-channel output, where the most common cases would be N = 1 and N = 2, and M = 2, and where:

  • you want to have a clear redistribution of energy when panning
  • you want to keep the stereo image for stereo files

There are a couple of parameters that could be interesting enough to expose them:

  • how much you spread the channels away from each other (in the common case, e.g. pan them together and mix them down, circle them, or balance them).
  • whether to move the audio signal to a new channel while panning (@bgold's preference was e.g. not to move it in the stereo case, but of course do move it in the mono case).
  • whether to connect the last and the first channel (wrap in a circle or not)

Tidal has a pan range from 0 to 1 and if I understood him correctly @yaxu suggested it might be a good idea to extend that range to -1 to 1, but to wrap in the stereo case, fore and back.

@muellmusik
Copy link

My suggestion would be the following:

  1. Where N == M hard assign.
  2. Where N != M splay without wrapping. By default maximise spread (meaning that for N > 1 first and last input channels should be hard panned to the first and last output channels) and evenly space (meaning that for odd values of N, you have a centre panned channel, including for mono).
  3. Provide a straightforward way to do other common options, i.e. mix down, wrap, etc.

My rationale for this is that it supports the most common cases out of the box (stereo or mono to stereo), but generalises well to different values of N and M. It requires no special treatments (note that 1. is actually just a particular case of 2., and does not require a separate implementation). Splaying, while perhaps not a 'standard' approach, is very useful in live coding once you wrap your mind around it. Apologies if this seems obvious, and hope it is of some use!

@telephon
Copy link
Contributor

In this approach, what would a pan parameter do? Would it move the channels in a ring? Would that include the stereo case?

@muellmusik
Copy link

pan could essentially be a centre in a generalised sense. So spread would mean width of the splay, and pan centring the splay. In the mono case spread should be ignored (or at least not used, as it complicates levels), and pan/centre is just location.

Yes, include the stereo case. My instinct is that the default should clip position rather than wrap as in a ring, since the case of panning a stereo field is more likely than surround systems. But a ring should be made very easy to do, maybe just a flag.

2 cents, anyway.

@telephon
Copy link
Contributor

telephon commented Dec 27, 2016

actually, so:

  • spread = 0 could mean each input maps to exactly one output (no pan across speakers)
  • spread = 1 could mean maximum spread (across all available)
  • spread = -1 could mean a mono mixdown.

Maybe a width parameter could somehow come close to what @kindohm described, but I'm not sure.

@muellmusik
Copy link

I would suggest spread 0 would mean a mixdown, panned to the location indicated by centre. Spread 1 is maximum spread. Sorry the comment above about levels has more to do with width (i.e. is a source panned across more than two adjacent inputs at any given position), so ignore that. -1 could be a special case of hard assigning channels until you run out of inputs (wrapping outputs?), but is that a likely generalisable use case?

I'd suggest mocking up a few interfaces and seeing how they feel in use.

@telephon
Copy link
Contributor

I think I've found a solution that has both behaviours. A parameter splay specifies whether you are spreading to the full number of channels or just to the maximum of th einput channels. This can be useful if you prefer to keep a 1-1 relationship between in and out: #40.

DirtSplayAz : UGen {

	*ar { arg numChannels, signals, spread = 1, pan = 0.0, mul = 1, splay = 1, width = 2, orientation = 0.5;
		var n, pos, channels;
		n = signals.size;
		if(n == 0) { Error("DirtSplay input has not even one channel. Can't pan no channel, sorry.").throw };
		spread = spread * splay.linlin(0, 1, n / numChannels, 1);
		pos = if(n == 1) { pan } { [ pan - spread, pan + spread ].resamp1(n) };
		channels = PanAz.ar(numChannels, signals, pos: pos, level: mul, width: width, orientation: orientation);
		^channels.flop.collect(Mix(_))
	}

}

@telephon
Copy link
Contributor

@kindohm
Copy link
Author

kindohm commented Jan 26, 2017

@telephon probably, but how do I test? I have superdirt installed currently. How do I overwrite my superdirt quark with this branch? Just clone the branch and copy-paste the files over the quark's files?

@bgold-cosmos
Copy link
Contributor

I've done something like this:

  • uninstall SuperDirt entirely
  • clone the GitHub repo wherever you like on your local machine
  • install it using the explicit path with something like Quarks.install("~/myGitHubFolder/SuperDirt");

@telephon
Copy link
Contributor

Yes, that's the correct way.

@kindohm
Copy link
Author

kindohm commented Jan 26, 2017

trying to test. but I can't get any folders of samples to get loaded.

@telephon
Copy link
Contributor

Now merged to 0.9-dev.

@telephon
Copy link
Contributor

@telephon
Copy link
Contributor

… for parameters like width and orientation.

@yaxu
Copy link
Collaborator

yaxu commented Feb 21, 2017

I think orientation isn't needed because you can add e.g. |+| pan "0.25" to offset all pans by a certain amount.
What do you think about making the default range for pan to go from -1 to 1, with 0 left and both 1 and -1 right? wrapping around in both directions

@telephon
Copy link
Contributor

@yaxu: no, a |+| pan "0.25" offset would offset everything by numChannels * 0.125. But you could use offset, of course (need to test if that works correctly in combination with the new panning scheme).

What do you think about making the default range for pan to go from -1 to 1, with 0 left and both 1 and -1 right? wrapping around in both directions

ok, good idea. Would that be the same for stereo, I suppose?

@yaxu
Copy link
Collaborator

yaxu commented Feb 21, 2017

Ok I'll look further into these parameters.

  • yes the idea would be to have the same for stereo and multichannel. I'm sure we discussed this before and think this may have been your suggestion originally. -1 .. 0 goes in the opposite direction from 0 .. 1

@telephon telephon changed the title stereo image in samples is lost when they are played let's sort out the panning scheme Feb 22, 2017
@telephon
Copy link
Contributor

Ah, now I remember. fixed here d0fd9c0

@kindohm
Copy link
Author

kindohm commented Feb 10, 2020

This was resolved quite a long time ago. Not sure what commit it was. Will log a new issue if a new concern comes up.

@kindohm kindohm closed this as completed Feb 10, 2020
@yaxu
Copy link
Collaborator

yaxu commented Oct 15, 2020

I'm having problems getting multichannel panning parameters to work. For example, using eight channels..

d1 $ slow 2 $ sound "clap*8" # pan saw # panorient (1/16)
  # pansplay "-1"
  # gain 1.4

Reading the above, I expected 1/16 to get added to the saw, and the pansplay "-1" to turn the stereo clap sound into mono. As it is, the above plays over three speakers, because it's a stereo sample centred on a single speaker.

The following plays over two speakers (adding the 1/16 straight to the pan):

d1 $ slow 2 $ sound "clap*8" # pan (saw |+ (1/16))
  # pansplay "-1"
  # gain 1.4

And this plays over one speaker (using the 'bd' sample which is already mono):

d1 $ slow 2 $ sound "bd*8" # pan (saw |+ (1/16))
  # pansplay "-1"
  # gain 1.4

Note that panorient actually gets sent in the osc as orientation and pansplay gets sent as splay.

@telephon
Copy link
Contributor

I'll take a look ...

@telephon
Copy link
Contributor

Some random questions first: Why do you have a pansplay of -1? It is clipped to 0 anyway.

	// span: how much the channels are distributed over the whole of numChannels. 0 means mixdown
	// splay: rescaling of span relative to the number of output channels

	// For splay = 1, and span = 1, the N synth channels should cover the whole output channel field.
	// For splay = 0, and span = 1, the N synth channels should cover only adjacent channels up to N of the output channels.
	// Intermediate values of splay give intermediate ranges.

But this is unrelated, given that I have this setup:

(
s.reboot { // server options are only updated on reboot
	// configure the sound server: here you could add hardware specific options
	// see http://doc.sccode.org/Classes/ServerOptions.html
	s.options.numBuffers = 1024 * 256; // increase this if you need to load more samples
	s.options.memSize = 8192 * 32; // increase this if you get "alloc failed" messages
	s.options.numWireBufs = 64; // increase this if you get "exceeded number of interconnect buffers" messages
	s.options.maxNodes = 1024 * 32; // increase this if you are getting drop outs and the message "too many nodes"
	s.options.numOutputBusChannels = 8; // <=== set this to your hardware output channel size, if necessary
	s.options.numInputBusChannels = 2; // set this to your hardware output channel size, if necessary
	// boot the server and start SuperDirt
	s.waitForBoot {
		~dirt = SuperDirt(8, s); // EIGHT output channels, increase if you want to pan across more channels
		~dirt.loadSoundFiles;   // load samples (path containing a wildcard can be passed in)
		// for example: ~dirt.loadSoundFiles("/Users/myUserName/Dirt/samples/*");
		// s.sync; // optionally: wait for samples to be read
		~dirt.start(57120, 0 ! 12);   // start listening on port 57120, create two busses each sending audio to channel 0

		// optional, needed for convenient access from sclang:
		(
			~d1 = ~dirt.orbits[0]; ~d2 = ~dirt.orbits[1]; ~d3 = ~dirt.orbits[2];
			~d4 = ~dirt.orbits[3]; ~d5 = ~dirt.orbits[4]; ~d6 = ~dirt.orbits[5];
			~d7 = ~dirt.orbits[6]; ~d8 = ~dirt.orbits[7]; ~d9 = ~dirt.orbits[8];
			~d10 = ~dirt.orbits[9]; ~d11 = ~dirt.orbits[10]; ~d12 = ~dirt.orbits[11];
		);
	};

	s.latency = 0.3; // increase this if you get "late" messages
};
);


I get a circle, using

d1 $ slow 2 $ sound "bd*8" # pan (saw |+ (1/16))
  # pansplay "0"
  # gain 1.4

@yaxu
Copy link
Collaborator

yaxu commented Dec 30, 2020

Sorry returning to this after some time! bd is a mono sample, if we use sd:

d1 $ slow 2 $ sound "sd*8" # pan (saw |+ (1/16))
  # pansplay "0"

.. then we get a circle but sd is played over a minimum of two speakers. I would like to work with discrete speakers where stereo samples get mixed down to mono. pansplay doesn't seem to have any effect actually, whatever value I put in there the result plays over neighbouring speakers. Neither # pansplay 0 # panspan 0 or # pansplay 1 # panspan 1 changes how either a mono or stereo sample is played. So maybe tidal's parameters are just not reaching the DirtSplayAz function for some reason?
pansplay sends the splay, and panspan sends the span parameter, which is what superdirt is expecting (I think I will just remove 'pan' prefixes from them then).

I think it was panspan I wanted to set to 0 rather than pansplay, but still neither has an effect for me.

@yaxu
Copy link
Collaborator

yaxu commented Dec 30, 2020

Ok I've worked out that it is working for stereo synths like superpiano but not for stereo samples like sd, so I think this is a bug.

(I'm still confused about what splay and span are doing)

@maxlouisr
Copy link

hey everyone ! I've been reading this through as i'm considering doing a multichannel show using Tidal for the first time. It will be with 16 speakers and i'm trying out a config using 16 output for Supercollider and Superdirt and checking it out with scope.

However, i still don't understand how to either activate and / or access any of these parameters - such as pansplay, splay, span etc. Are they integrated in the current version of Superdirt or do i need to do something on the Supercollider side as well ?

The main thing i'm trying to do is send a sound to all outputs in a balanced way. Is there a built in SplayAz or equivalent in Superdirt that would allow to spread a sample or synth across all channels in balance - or what would be the appropriate way to have a single sound, whether mono or stereo, spread out throughout all channels without phasing issues ?

Panning between 0 and 1 works great so i'm just wondering how to alternatively tighten the output to one channel or spread it out to all channels to dilate and compress the sound.

This tidal code mentioned above (let (span, span_p) = pF "span" Nothing) yields error messages.

let (span, span_p) = pF "span" Nothing
(splay, splay_p) = pF "splay" Nothing
(panWidth, panWidth_p) = pF "panwidth" Nothing
(orientation, orientation_p) = pF "orientation" Nothing)

So I've just tried on my end with a simple let splay = pF "splay" etc.
Is this still the way to access these parameters ? I can't see anything related in the boottidal file either.

And i have another question regarding custom FXs but maybe after all this !

Many many thanks x

@yaxu
Copy link
Collaborator

yaxu commented Apr 16, 2024

Hi @maxlouisr,

The code for making extra parameters has simplified over the years. Try this:

let span = pF "span"
    splay = pF "splay"
    panWidth = pF "panWidth"
    orientation = pF "orientation"

and let us know how you get on!

@yaxu
Copy link
Collaborator

yaxu commented Apr 16, 2024

Ah reading more, I see you already tried that. As far as I know, these should still work, but when I last tried some years, I had difficulty getting panwidth/splay to work. Perhaps there was something I didn't understand.

@maxlouisr
Copy link

Hey @yaxu thanks for getting back ! No indeed i'd tried that already but nothing happens that i could see - none of these parameters seem to affect the panning of either mono or stereo samples.

However i'd overlooked testing it out on synths, for which there seem to be mild differences - the parameter 'orientation' works as expected on mono synths. The others don't seem to do anything that i could see (i tried superfm and supermandolin).

On a stereo synth, all parameters have a behaviour but it's odd. I can't quite wrap my head around it, not does it correspond to my experience of these tools (PanAz, SplayAz) on the supercollider side. From what i see they they occasionally send out the sound to a third or four channel at best depending on parameter value.

@yaxu
Copy link
Collaborator

yaxu commented Apr 17, 2024

Hi @maxlouisr I don't have a multichannel sound system to hand but just tried this by looking at the supercollider scope with 16 channels configured.

I see the same behaviour, nothing works for samples (mono or stereo) but does for synths.

I could get panwidth to work, i.e. without the capital M.. again only for synths (I tried with supersaw). But splay didn't seem to have an effect in any case.

@telephon has this worked for you recently?

@maxlouisr
Copy link

Hey ! Yes ok that's good to know was using with capital. It looks like around panwidth 20/30 you can send out to most channels.

I was playing code yesterday looking at the scope. First of all really enjoyed the effect of playing multichannel on stereo ! it's really cool - you get this offscreen presence as sounds vanish either left or right.
But also thought it might be worth considering a default for multichannel that would be 'all out' when pan isn't invoked. Like for sterero, no pan (= pan 0.5?) = both channels. It would make code more flexible / translateable if no pan would send out to all channels in multi, I don't know if that's at all possible by tweaking the AZ Ugens to respond in width/spread to the number of output channels for instance.

I also made a quick test using custom pan and splay effect and it seems to work surprisingly well (including with routings and effects), but i'm guessing this would mean trouble somewhere down the line ?

~dirt.addModule('splaz', { |dirtEvent|
dirtEvent.sendSynth('splaz' ++ ~dirt.numChannels,
[
splaz: ~splaz,
out: ~out
]

)}, { ~splaz.notNil });

SynthDef("splaz" ++ ~dirt.numChannels, {
|out|
var dry, wet;
dry = In.ar(out, ~dirt.numChannels);
wet = SplayAz.ar(~numChannels, dry.sum, 1, 1, \splaz.kr(16).linlin(0, 1, 0, 16));
ReplaceOut.ar(out, wet);
}, [\ir]).add;

~dirt.addModule('panaz', { |dirtEvent|
dirtEvent.sendSynth('panaz' ++ ~dirt.numChannels,
[
panaz: ~panaz,
panazwidth: ~panazwidth,
panazlag: ~panazlag,
out: ~out
]

)}, { ~panaz.notNil });

SynthDef("panaz" ++ ~dirt.numChannels, {
|out|
var dry, wet, lag;
lag = \panazlag.kr(0);
dry = In.ar(out, ~dirt.numChannels);
wet = PanAz.ar(16, dry.sum, \panaz.kr(0, lag) / 8, 1, \panazwidth.kr(2.0, lag), 0);
ReplaceOut.ar(out, wet);
}, [\ir]).add;

@telephon
Copy link
Contributor

@telephon has this worked for you recently?

I haven't tried. But we can try it out, probably in two weeks.

@maxlouisr
Copy link

Hey, I'm on site now but can't figure a good way to get a centered sound (coming out simultaneously from all speakers). Setting the channel paramater to [0,2,4,6,8,10,12,14] makes it louder as the signal is sent out full blast to each speaker rather than going through a panning process with level compensation. And I can’t find a way to use the span or splay paramaters, neither on synths or samples. The only thing that works so far is my own custom SplayAz effect above, although it mixes down the input to mono.
Is there anything that I'm missing ? Or anything i can do to help troubleshoot this ?
Let me know if i can check out other functionalities whilst i'm here. This came out much clearer by ear than with scope for instance.

@yaxu
Copy link
Collaborator

yaxu commented May 14, 2024

Sorry I'm not a supercollider/superdirt expert..

Maybe not useful, but the last time I played on a multichannel system (Amoenus soundsystem in London), rather than sending one channel per speaker, I sent 8 mono channels. I then sent OSC messages to control the xyz, spread, doppler, presence, yaw and heaviness for each of those channels. Just in case you have a similar ambisonic mixer thingie that can be remote controlled like this.

Here's the config I made for that, in case it's somehow useful..

// >>> SUPERDIRT CONFIG STARTS

(
s.reboot {
    s.options.maxLogins = 4;
    s.options.numBuffers = 1024 * 256; // increase this if you need to load more samples
    s.options.memSize = 8192 * 32; // increase this if you get "alloc failed" messages
    s.options.numWireBufs = 64; // increase this if you get "exceeded number of interconnect buffers" messages
    s.options.maxNodes = 1024 * 32; // increase this if you are getting drop outs and the message "too many nodes"
    s.options.numOutputBusChannels = 8;
    s.options.numInputBusChannels = 0;
    // boot the server and start SuperDirt
    s.waitForBoot {
        ~dirt.stop;
        ~dirt = SuperDirt(1, s); // mono
        ~dirt.start(57120, [0,1,2,3,4,5,6,7]);   // 8 mono outputs
        ~dirt.loadSoundFiles;
        //~dirt.loadSoundFiles("~/mysamples/*");
    };

        s.latency = 0.3; // increase this if you get "late" messages

};
);



-- >>> TIDAL CONFIG 

:set -XOverloadedStrings
:set prompt ""

import Sound.Tidal.Context

import System.IO (hSetEncoding, stdout, utf8)
hSetEncoding stdout utf8


:{
let spattarget = Target {oName = "spat",
                         oAddress = "192.168.1.90",
                         oPort = 2222,
                         oLatency = 0.05,
                         oWindow = Nothing,
                         oSchedule = Live,
                         oHandshake = False,
                          oBusPort = Just 57110
                        }
    spatformats = [OSC "/source/{sorbit}/mute"  $ ArgList [("mute", Nothing)],
                   OSC "/source/{sorbit}/aed"   $ ArgList [("a", Nothing), ("e", Nothing), ("d", Nothing)],
                   OSC "/source/{sorbit}/xyz"   $ ArgList [("x", Nothing), ("y", Nothing), ("z", Nothing)],
                   OSC "/source/{sorbit}/spread"   $ ArgList [("spread", Nothing)],
                   OSC "/source/{sorbit}/doppler"   $ ArgList [("doppler", Nothing)],
                   OSC "/source/{sorbit}/pres"   $ ArgList [("pres", Nothing)],
                   OSC "/room/{sorbit}/heaviness" $ ArgList [("heaviness", Nothing)],
                   OSC "/source/{sorbit}/yaw"   $ ArgList [("yaw", Nothing)]
                  ]
    mute = pI "mute"
    a = pF "a"
    e = pF "e"
    d = pF "d"
    x = pF "x"
    y = pF "y"
    z = pF "z"
    spread = pF "spread"
    sorbit = pI "sorbit"
    doppler = pF "doppler"
    pres = pF "pres"
    heaviness = pF "heaviness"
    yaw = pF "yaw"
    oscmap = [(spattarget, spatformats),
              (superdirtTarget {oLatency = 0.05, oAddress = "127.0.0.1", oPort = 57120},
               [superdirtShape]
              )
             ]
:}

tidal <- startStream (defaultConfig {cCtrlAddr = "0.0.0.0", cVerbose = True, cFrameTimespan = 1/20}) oscmap

:{
let only = (hush >>)
    p = streamReplace tidal
    hush = streamHush tidal
    panic = do hush
               once $ sound "superpanic"
    list = streamList tidal
    -- mute = streamMute tidal -- used by spat
    unmute = streamUnmute tidal
    unmuteAll = streamUnmuteAll tidal
    unsoloAll = streamUnsoloAll tidal
    solo = streamSolo tidal
    unsolo = streamUnsolo tidal
    once = streamOnce tidal
    first = streamFirst tidal
    asap = once
    nudgeAll = streamNudgeAll tidal
    all = streamAll tidal
    resetCycles = streamResetCycles tidal
    setCycle = streamSetCycle tidal
    setcps = asap . cps
    getcps = streamGetcps tidal
    getnow = streamGetnow tidal
    xfade i = transition tidal True (Sound.Tidal.Transition.xfadeIn 4) i
    xfadeIn i t = transition tidal True (Sound.Tidal.Transition.xfadeIn t) i
    histpan i t = transition tidal True (Sound.Tidal.Transition.histpan t) i
    wait i t = transition tidal True (Sound.Tidal.Transition.wait t) i
    waitT i f t = transition tidal True (Sound.Tidal.Transition.waitT f t) i
    jump i = transition tidal True (Sound.Tidal.Transition.jump) i
    jumpIn i t = transition tidal True (Sound.Tidal.Transition.jumpIn t) i
    jumpIn' i t = transition tidal True (Sound.Tidal.Transition.jumpIn' t) i
    jumpMod i t = transition tidal True (Sound.Tidal.Transition.jumpMod t) i
    jumpMod' i t p = transition tidal True (Sound.Tidal.Transition.jumpMod' t p) i
    mortal i lifespan release = transition tidal True (Sound.Tidal.Transition.mortal lifespan release) i
    interpolate i = transition tidal True (Sound.Tidal.Transition.interpolate) i
    interpolateIn i t = transition tidal True (Sound.Tidal.Transition.interpolateIn t) i
    clutch i = transition tidal True (Sound.Tidal.Transition.clutch) i
    clutchIn i t = transition tidal True (Sound.Tidal.Transition.clutchIn t) i
    anticipate i = transition tidal True (Sound.Tidal.Transition.anticipate) i
    anticipateIn i t = transition tidal True (Sound.Tidal.Transition.anticipateIn t) i
    forId i t = transition tidal False (Sound.Tidal.Transition.mortalOverlay t) i
    d1 = p 1 . (|< orbit 0) . (|< sorbit 1)
    d2 = p 2 . (|< orbit 1) . (|< sorbit 2)
    d3 = p 3 . (|< orbit 2) . (|< sorbit 3)
    d4 = p 4 . (|< orbit 3) . (|< sorbit 4)
    d5 = p 5 . (|< orbit 4) . (|< sorbit 5)
    d6 = p 6 . (|< orbit 5) . (|< sorbit 6)
    d7 = p 7 . (|< orbit 6) . (|< sorbit 7)
    d8 = p 8 . (|< orbit 7) . (|< sorbit 8)
    d9 = p 9 . (|< orbit 8)
    d10 = p 10 . (|< orbit 9)
    d11 = p 11 . (|< orbit 10)
    d12 = p 12 . (|< orbit 11)
    d13 = p 13
    d14 = p 14
    d15 = p 15
    d16 = p 16
    sjux f pat = jux (f . (|+ orbit 1) . (|+ sorbit 1)) pat
    juxa :: [t -> Pattern ValueMap] -> t -> Pattern ValueMap
    juxa fs p = stack $ map (\n -> (fs !! n) p |+ orbit (pure n) |+ sorbit (pure n) |+ a (pure $ 360 * (fromIntegral n / fromIntegral l) )) [0 .. l-1]
      where l = length fs

    juxe :: [t -> Pattern ValueMap] -> t -> Pattern ValueMap

    juxe fs p = stack $ map (\n -> (fs !! n) p |+ orbit (pure n) |+ sorbit (pure n) |+ e (pure $ 360 * (fromIntegral n / fromIntegral l) )) [0 .. l-1]

          where l = length fs

    juxd :: [t -> Pattern ValueMap] -> t -> Pattern ValueMap

    juxd fs p = stack $ map (\n -> (fs !! n) p |+ orbit (pure n) |+ sorbit (pure n) |+ d (pure $ 0.8 + 0.4 * (fromIntegral n / fromIntegral l) )) [0 .. l-1]

          where l = length fs

    -- This is a guess!

    _spina copies p =

      stack $ map (\i -> let offset = toInteger i % toInteger copies in

                         offset `_early` p

                         # a (pure $ 360 * (fromRational offset))

                  )

              [0 .. (copies - 1)]

    spina = _patternify _spina

:}

:{
let getState = streamGet tidal
    setI = streamSetI tidal
    setF = streamSetF tidal
    setS = streamSetS tidal
    setR = streamSetR tidal
    setB = streamSetB tidal
:}

:set prompt "tidal> "
:set prompt-cont ""

default (Pattern String, Integer, Double)

@maxlouisr
Copy link

hi alex thanks for you response ! This doesn't really apply to the context i'm in but thanks for taking the time to share just in case.

@telephon
Copy link
Contributor

You can set the global amplitude: ~dirt.set(\amp, 1/8)
If you want to re-route the output of supercollider, you can do this also at the very last stage, you could do a variant of this: https://scsynth.org/t/multichannel-best-practices/5599/3

But maybe you want this as an effect?

You could then just write a module that does such a mapping.

SynthDef("dirt_mono" ++ ~dirt.numChannels, { |out|
		var orig, signal, n;
		n = ~dirt.numChannels;
		orig = In.ar(out, n);
		signal = orig.sum.dup(n) * (1/n);
		ReplaceOut.ar(out, signal);
	}, [\ir]).add;

~dirt.addModule('mono',
	{ |dirtEvent|
		dirtEvent.sendSynth("dirt_mono" ++ ~numChannels,
			[
				out: ~out
		])

}, { ~mono.notNil });

@maxlouisr
Copy link

Hi Julian, thanks a lot for your response.
Lmk if i'm wrong but by looks of it and trying it out this only mixes down to mono on one channel ?
What i haven't found yet is an efficient way to spread out a sound / orbit to all channels. Doing pan "[0,2,4,6..." seems to duplicate the signal / audio busses, which leads to much higher cpu (and also louder volume).
In supercollider what i do is use SplayAz with a large number for width (at least the number of channels out). This is an empirical and approximate solution, it doesn't do a clean equal volume spread on all channels, but it does work well enough without eating up too much CPU.

@telephon
Copy link
Contributor

telephon commented Jun 3, 2024

Let's say your SuperDirt instance has 16 channels. Then,

signal = orig.sum.dup(n) * (1/n);

sums all existing channels of your synth and sends the mix to all 16 output channels.

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

No branches or pull requests

7 participants