SoundFileView

What would be the simplest way to display only the left channel from a stereo audio file in a SoundFileView?
Thanks.

AFAIK there is no built in method, language-side, to de-interleave.

// language-side only way (no server)
(
~readSFChannel = { |path, channel = 0, startFrame = 0, numFrames = -1|
    var file = SoundFile.openRead(path);
    var out, chunk, chunkSize = 1 << 16, i = 0;
    if(file.notNil) {
        protect {
            if(numFrames < 0) {
                numFrames = file.numFrames - startFrame;
            };
            out = Signal.newClear(numFrames);
            file.seek(startFrame);
            chunkSize = chunkSize * file.numChannels;  // ensure multiple
            while {
                numFrames > 0
            } {
                chunk = Signal.newClear(chunkSize);
                file.readData(chunk);
                if(chunk.size == 0) {
                    numFrames = 0
                } {
                    forBy(channel, chunk.size-1, file.numChannels) { |j|
                        out[i] = chunk[j];
                        i = i + 1;
                    };
                    numFrames = numFrames - (chunk.size div: file.numFrames);
                }
            };
            out
        } {
            file.close;
        }
    } {
        "'%' open failed".format(path.basename).warn;
        nil
    }
};
)

Or, readChannel into the server and then loadToFloatArray.

hjh

1 Like

sox seems to be able to do a few things you can’t with SoundFile yet. For example:

sox input_audio_file.wav output_just_left_channel.wav remix 1

After running this command, you’ll have a new audio file, output_just_left_channel.wav, that contains only the left channel from the original input file. It could be a temporary file in a /tmp folder, if you just want to visualize it.

Someone probably wrote a wrapper for the sox program. If not, that could be useful.

edit: SoX can input/output audio to stdout, making it potentially useful for piping audio data to other commands. Integration with sclang to read it could be interesting

Since I had just posted an example using SoundFile to extract one channel from an audio file into a Signal object that’s suitable for SoundFileView’s setData method, it would be more accurate to say that sox does some things for which SoundFile doesn’t provide an out-of-the-box method.

I do understand your meaning – the lack of a convenience method for this, combined with the C-style while idiom for reading the file data (which is less familiar to most SC users), makes it just that little bit extra PITA where you’d reach for sox rather than writing a function as I did. Just reminding that “it isn’t immediately convenient with SoundFile” is not the same meaning as “you can’t do it with SoundFile.”

hjh

1 Like

You showed one way, it could be part of the class maybe.

BUT while loops are less intuitive and messier, not just in SuperCollider, but in general. Aren’t they? What if a general function/, something like (pseudo code): readInChunks(getChannel) or readData(select: channel). or file.readData(framesToRead).collect { |frame| frame[channel % file.numChannels] } or replicateM size (hGetElement handle) etc

just thinking in general terms about it…

sox could be an inspiration for soundfile, in some ways…


# Extract one channel from a multichannel file (numbering is from 1)
sox stereo.wav right.wav remix 2

# Extract the first, third and fifth channels from a multichannel file
sox multichannel.wav subset.wav remix 1 3 5


# Generate a spectrogram (output to spectrogram.png)
sox speech.wav -n spectrogram

# Process the audio contents to calculate properties such as RMS.
sox input.wav -n stats

# Splitting audio based on silence
sox in.wav out.wav silence 1 0.5 1% 1 5.0 1% : newfile : restart

Thanks for the replies. Wasn’t sure if I’d missed something, but those examples will sort it.

Yes, very old school.

Streams, such as those made by patterns, can be pressed into service as more modern-style iterators; a SoundFile:asStream method would make a lot of SoundFile problems go away, and be generally applicable.

I was also reacting to the notion that one might just give up and look at other software if a pre-packaged method doesn’t already exist. It’s more empowering IMO to frame such issues as “there isn’t a method for it specifically, but you can make a method or function by doing x…” and empowerment is the whole point of computer languages that are meant for play, as SC is. while is terribly old fashioned but it’s available and it can get you where you need to go, even if there’s no other way.

hjh

I would never thought of that, might be very interesting.

My first try would be treating Signal as a data structure that can be folded (crop (segmenting a sound according to a bool (check for long silences, for example), concat (to join many files). Even a filter could be defined this way if you apply feedback and feed-forward vector lists (once you know this). No while loops anywhere, I believe.

Also, extract more information from the file, like RMS (which can just be a fold like the others rms samples = sqrt (foldr (\sample acc -> acc + sample^2) 0 samples / length samples).

SoundFile could provide a basic collection of features that are not easy to find now.

well, it could develop in other directions too, of course…

Yes, you’re not missing anything, the SoundFile and SoundFileView interfaces are bad for a high level language. They are mostly just wrappers for the libsndfile and Qt interfaces respectively, and there hasn’t been anyone to put in the effort to design anything better.

1 Like

For giggles:

+ SoundFile {
	// UnixFILE supports `next`, why not SoundFile?
	// (but this requires a new instance variable `stream`,
	// which isn't possible to define in a class extension
	// For testing, I'd just written these into the main class def
	next {
		if(stream.isNil) {
			stream = this.asStream;
		};
		^stream.next
	}

	nextN { |n = 1|
		var out = Signal(n);
		n.do {
			var sample = this.next;
			// EOF retrieves nil
			// aSignal.add(nil) will barf with "wrong type"
			// so we bail out at EOF
			if(sample.isNil) {
				^out
			};
			out.add(sample);
		};
		^out
	}

	reset {
		this.seek(0, 0);
		stream = this.asStream;
	}

	asStream {
		^Routine {
			var chunkSize = 65536, chunk;
			while {
				chunk = Signal.newClear(chunkSize);
				this.readData(chunk);
				chunk.size > 0
			} {
				chunk.do(_.yield);
			};
		}
	}
}

And some pattern-y goodness:

PeveryNth : FilterPattern {
	var <>cycle = 1;

	*new { |pattern, cycle|
		^super.new(pattern).cycle_(cycle)
	}

	embedInStream { |inval|
		var stream = pattern.asStream;
		var cycleStream = cycle.asStream;
		var cleanup = EventStreamCleanup.new;
		var value, thisCycle;

		loop {
			thisCycle = cycleStream.next(inval);
			if(thisCycle.isNil) {
				^cleanup.exit(inval)
			};

			value = stream.next(inval);
			if(value.isNil) {
				^cleanup.exit(inval)
			};
			cleanup.update(value);
			inval = value.yield;

			max(0, thisCycle - 1).do {
				value = stream.next(inval);
				if(value.isNil) {
					^cleanup.exit(inval)
				};
				cleanup.update(value);
			};
		}
	}
}

// couple overrides
+ Stream {
	all { arg inval, class(Array);
		// don't do this on infinite streams.
		var array = class.new;
		this.do({|item| array = array.add(item) }, inval);
		^array
	}
}

+ Object {
	nextN { arg n, inval, class(Array);
		^class.fill(n, { this.next(inval) });
	}
}

Then:

f = SoundFile.openRead(Platform.resourceDir +/+ "sounds/SinedPink.aiff");
x = PeveryNth(Pdrop(0 /* channel index */, f), f.numChannels).asStream.all(class: Signal);
f.close;

VoilĂ , de-interleaved, using a proper iterator, and making it possible to de-interleave patterns in general.

This isn’t a fully-thought-out “new feature,” more of a sketch or proof of concept – but it works.

hjh

1 Like

James, you’re the Jedi master of the Pattern language.