def realise(self, audio): # TODO do multiple channels at the same time? # when given a single gain for multiple channels, apply it to all of them if len(self.amps) == 1 and audio.num_channels > 1: amps = self.amps * audio.num_channels else: amps = self.amps for (i, amp) in enumerate(amps): # TODO shouldn't this just affect a copy of audio???? # above comment was previously for the line: # audio.audio = self.size*audio.audio if isnumber(amp): audio.audio[i, :] *= amp elif isinstance(amp, Curve): vals = amp.flatten(audio.sample_rate) # TODO view or copy? # TODO what if curve duration doesn't match signal? # or can we have curve duration extracted from signal? automatically matching it # should we just stretch the last value of curve? # should we define curve.conform? # also what if curve is too long? audio.audio[i, 0:amp.num_samples(audio.sample_rate)] *= vals audio.audio[ i, amp.num_samples(audio.sample_rate):] *= amp.endpoint() else: raise TypeError("Unsupported amplitude type")
def __ror__(self, other): if other == 0: return self if isnumber(other): return Silence(duration=other)._concat(self) raise TypeError( "Signal can only be concatted to other signals, or to 0.")
def realise(self, audio): # or maybe we can string some monos together and apply same panning for all? assert audio.num_channels == 1, "panning is from mono to multi" if isnumber(self.pan): dBs = self.scheme(self.pan) elif isinstance(self.pan, Curve): dBs = self.scheme(self.pan.flatten(audio.sample_rate)) audio.from_mono(len(dBs)) for (i, dB) in enumerate(dBs): if isnumber(dB): audio.audio[i, :] *= DB_to_Linear(dB) else: # paramterization audio.audio[i, :len(dB)] *= DB_to_Linear(dB) audio.audio[i, len(dB):] *= DB_to_Linear( self.scheme(self.pan.endpoint())[i])
def __or__(self, other): # concat with number is casted to prolongation of the final value # TODO this is one of the few points inconsistent with Signal! if isnumber(other): other = Constant(value=self.endpoint(), duration=other) c = CompoundCurve() if isinstance(self, CompoundCurve): c.curves += self.curves else: c.curves += [self] if isinstance(other, CompoundCurve): c.curves += other.curves else: c.curves += [other] return c
def realise(self, audio): # should we make this inherit from amplitude? like Triangle/Sine relation? # when given a single gain for multiple channels, apply it to all of them if len(self.dBs) == 1 and audio.num_channels > 1: dBs = self.dBs * audio.num_channels else: dBs = self.dBs for (i, dB) in enumerate(dBs): if isnumber(dB): audio.audio[i, :] *= DB_to_Linear(dB) elif isinstance(dB, Curve): vals = DB_to_Linear(dB.flatten(audio.sample_rate)) audio.audio[i, 0:dB.num_samples(audio.sample_rate)] *= vals audio.audio[ i, dB.num_samples(audio.sample_rate):] *= DB_to_Linear( dB.endpoint()) else: raise TypeError("Unsupported amplitude type")
def _apply(self, transform): assert not isinstance( transform, BiTransform), "can't apply BiTransform, use concat." if transform is None: return self if isnumber(transform): return self._amplitude(transform) if iscallable(transform) and not isinstance(transform, Transform): # if transform is a function receiving a signal and returning a signal, # masquerading as a Transform. Use @transform decorator for those return transform(self) s = self.copy() # TODO is this needed? # it is, so we can reuse the same base signal multiple times if isinstance(transform, TransformChain): s.transforms.extend(transform.transforms) else: s.transforms.append(transform) return s
def _mix(self, other): # mixing with constant number is interpreted as DC # TODO use @singledispatchmethod for this? if isnumber(other): # TODO not all Signals define self.duration! # This looks like a case for duration inference # luckily this isn't actually a very useful feature # (though it may be for Curve?) other = other * DC(duration=self.duration) s = Mix() if not self.transforms and isinstance(self, Mix): s.signals += self.signals else: s.signals += [self] if not other.transforms and isinstance(other, Mix): s.signals += other.signals else: s.signals += [other] return s
def _concat(self, other): # TODO why is this before mix? # TODO consider enabling for negative other, # thus shifting a sequence forward(backward? Chinese) in time # TODO possibly better way to implement than using Silence if other is None: return self if isnumber(other): other = Silence(duration=other) s = Sequence() # use isinstance(self, Sequence) instead? more semantic # TODO write this as overloading of Sequence operators instead? if not self.transforms and isinstance(self, Sequence): s.sequence += self.sequence else: s.sequence += [self] if isinstance(other, BiTransform): # if concatting BiTransform s.sequence[-1] = s.sequence[-1]._apply(other.L) s.sequence += [other.R] return s if isinstance(s.sequence[-1], Transform): # if coming out of BiTransform t = s.sequence.pop() else: t = None if not other.transforms and isinstance(other, Sequence): other.sequence[0] = other.sequence[0]._apply(t) s.sequence += other.sequence else: s.sequence += [other._apply(t)] return s
def _amplitude(self, number): assert isnumber(number) return self * Amplitude(number)