Miscellaneous Tools

for manipulating Sound Objects

This notebook contains some tools which you may find useful when working with Sound objects.

Generating SampledSoundList Objects From SampledSoundFunction Objects

Occasionally, it becomes desirable to obtain a SampledSoundList object in order to manipulate the sound represented by a SampledSoundFunction object.

The SampledSoundList object has some advantages over a SampledSoundFunction object.  The principal advantage is that the actual sound samples may be manipulated, to perform Fast Fourier Transforms (FFT's) and such, in order to perform such tasks as reverberation, echo and filtering.

Also, any time the sound is to be played, the actual samples must be computed.  In a SampledSoundFunction object, this computation must be repeated whenever the sound is to be played.  In a SampledSoundList object, the samples need not be recomputed; they are already part of the object.

FunctionToList[sound_SampledSoundFunction] :=
	Module[{samps},
		samps = Array[sound[[1]], sound[[2]]];
		samps /= (Max[Abs[samps]] + 0.00001);
		Return[SampledSoundList[samps, sound[[3]]]]
	]

sinf = Play[Sin[2 Pi 1000 t], {t, 0, 0.05}, SampleRate->22050]

sinl = FunctionToList[sinf[[1]]];

Show[Sound[sinl]]

Obviously, the SampledSoundFunction object has its advantages as well:

ByteCount[sinf]

ByteCount[sinl]

Mixing SampledSoundList Objects

Often, when you have generated many sounds, it would be convenient to hear them simultaneously.  Below is a function, SoundMix, which combines several SampledSoundList objects into one SampledSoundList object which represents all of the component sounds played together.

SoundMix::srate = "Cannot mix sounds with differing sample rates.";

SoundMixTest[s_List] := 
	If[(Length[Union[(#[[2]])& /@ s]]) == 1,
		True,
		Message[SoundMix::srate]; False
	]

SoundMix[sound___SampledSoundList /; SoundMixTest[{sound}]] :=
	Module[{samps, newlen, i},
		If[Length[{sound}] < 1,
			Return[SampledSoundList[{0.0},
				SampleRate /. Options[Play]]]
		];
		samps = First /@ {sound};
		newlen = Max[Length /@ samps];
		For[i = 1, i <= Length[samps], i++,
			samps[[i]] = Join[samps[[i]],
				Table[0.0, {newlen - Length[samps[[i]]]}]]
		];
		samps = Plus @@ samps;
		samps /= (Max[Abs[samps]] + 0.00001);
		Return[SampledSoundList[samps, {sound}[[1, 2]]]]
	]

low = SampledSoundList[Table[N[Sin[2 Pi 400 n/22050.0]], {n, 6615}], 22050];

Show[Sound[low]]

med = SampledSoundList[Table[N[Sin[2 Pi 800 n/22050.0]], {n, 4410}], 22050];

Show[Sound[med]]

hi = SampledSoundList[Table[N[Sin[2 Pi 1600 n/22050.0]], {n, 2205}], 22050];

Show[Sound[hi]]

all = SoundMix[low, med, hi];

Show[Sound[all]]

Using the FunctionToList function defined above, it is now trivial to extend this function to operate on SampledSoundFunction objects, as well:

SoundMix[sound___SampledSoundFunction /; SoundMixTest[{sound}]] :=
	SoundMix @@ (FunctionToList /@ {sound})

And, by using just a little more trickery, we can get it to work on a list that contains both kinds of sound objects:

SoundMix::head = "The arguments to SoundMix must have Heads of either SampledSoundList or SampledSoundFunction.";

SoundMixList[s_List] :=
	If[MemberQ[{{SampledSoundList, SampledSoundFunction},
			{SampledSoundFunction, SampledSoundList}},
			Union[Head /@ s]],
		True,
		Message[SoundMix::head]; False
	]

SoundMix[sound___ /; SoundMixList[{sound}]] :=
	Module[{dummy, i},
		dummy = {sound};
		For[i = 1, i <= Length[dummy], i++,
			If[Head[dummy[[i]]] =!= SampledSoundList,
				dummy[[i]] = FunctionToList[dummy[[i]]]
			]
		];
		Return[SoundMix @@ dummy]
	]

Concatenating SampledSoundList Objects

Often, when you have generated many sounds, it would be convenient to hear them in succession.  Below is a function, SoundSplice, which combines several sound objects into one SampledSoundList object which represents all of the component sounds played in succession.

SoundSplice::srate = "Cannot splice sounds with differing sample rates.";

SoundSpliceTest[s_List] := 
	If[(Length[Union[(#[[2]])& /@ s]]) == 1,
		True,
		Message[SoundSplice::srate]; False
	]

SoundSplice[sound___SampledSoundList /; SoundSpliceTest[{sound}]] :=
	Module[{samps},
		If[Length[{sound}] < 1,
			Return[SampledSoundList[{0.0},
				SampleRate /. Options[Play]]]
		];
		samps = First /@ {sound};
		samps = Join @@ samps;
		Return[SampledSoundList[samps, {sound}[[1, 2]]]]
	]

SoundSplice[sound___SampledSoundFunction /; SoundSpliceTest[{sound}]] :=
	SoundSplice @@ (FunctionToList /@ {sound})

SoundSplice::head = "The arguments to SoundSplice must have Heads of either SampledSoundList or SampledSoundFunction.";

SoundSpliceList[s_List] :=
	If[MemberQ[{{SampledSoundList, SampledSoundFunction},
			{SampledSoundFunction, SampledSoundList}},
			Union[Head /@ s]],
		True,
		Message[SoundSplice::head]; False
	]

SoundSplice[sound___ /; SoundSpliceList[{sound}]] :=
	Module[{dummy, i},
		dummy = {sound};
		For[i = 1, i <= Length[dummy], i++,
			If[Head[dummy[[i]]] =!= SampledSoundList,
				dummy[[i]] = FunctionToList[dummy[[i]]]
			]
		];
		Return[SoundSplice @@ dummy]
	]

low = SampledSoundList[Table[N[Sin[2 Pi 400 n/22050.0]], {n, 6615}], 22050];

Show[Sound[low]]

med = SampledSoundList[Table[N[Sin[2 Pi 800 n/22050.0]], {n, 4410}], 22050];

Show[Sound[med]]

hi = SampledSoundList[Table[N[Sin[2 Pi 1600 n/22050.0]], {n, 2205}], 22050];

Show[Sound[hi]]

inarow = SoundSplice[low, med, hi];

Show[Sound[inarow]]

Delaying sounds

Occasionally, it can be handy to cause a sound to have silence at its start.  This can be used when combining sounds, to cause sounds to occur sequentially instead of simultaneously.

DeltaT[sound_SampledSoundList, dt_] :=
	SampledSoundList[
		Join[Table[0.0, {dt * sound[[2]]}], sound[[1]]],
		sound[[2]]
	]

DeltaT[sound_SampledSoundFunction, dt_] :=
	DeltaT[FunctionToList[sound], dt]

Show[Sound[DeltaT[hi, 0.1]]]

Show[Sound[DeltaT[sinf[[1]], 0.05]]]

Show[Sound[SoundMix[sinf[[1]], DeltaT[hi, 0.05], DeltaT[sinf[[1]], 0.15]]]]