== Signal processors == fix tests of Plain.Control tests for filter step functions: test equivalence of different implementations Dimensional.RateAmplitude.Filter In contrast to Dimensional.Rate.Filter we could and should support initial values. Plain.Control - inconsistency between linear and exponential 'linear' expects slope, 'exponential' expects time, exponential could well expect a kind of slope, or linear could expect a time. What is the best way to go? oscillator with Signal.Cyclic.Amplitude as wave amplitude is forwarded to the result signal The original sample rate of the wave signal is however irrelevant which shall be expressed in the types oscillator with band-limited waves also for sampled waves: pyramid filtering on the wave waveforms with band limitation wave :: p -> p -> v Where the first 'p' denotes the phase step size with which the wave is going to be used Here we can re-use the morphing waves, e.g. asymmetric triangle as bandlimited saw, trapezoid as bandlimited square Control: convert Piecewise into a function, in order to use such curves as oscillator waves distortion by table This task should be divided into 1. converting an array to a function by interpolation, 2. apply a function to all values of a signal. The second process will be frequently used alone, because many distortion functions can be implemented more easily by a simple algorithm. fast one-pass histogram using a dynamic array Use the lazy ST monad? ToneModulation non-negative type for relative shape parameters as soon as NumericPrelude supports this a better way quantization two resampling stages: block-wise integration and stretching == Examples == mix the envelopes of instruments in a song and use this as cut-off control of a filter However, I do not want to use an envelope follower of the mixed signal, but mix the envelopes separately. This means a bit more of logistic effort. We may turn (EventList Note) into (EventList (Sound, Envelope)), then mix the sounds and envelopes separately. abuse ToneModulation to get a tone out of an arbitrary short noise, by stretching its time axis by a large factor play a melody with a string sound but alter the octave within one tone by an arbitrary pattern soften a natural sound like human voice and use this as input for FM synthesis, e.g. let sine = smooth voice in fmOsci sine sine let sine = smooth voice in fmOsci (map (^2) sine) sine In the second example the modulator has an DC offset, but this would only yield a phase shift in the modulated signal discretize a sine wave and use this to control the inversions of a chord should sound like a discretized sweep mass spectra usually have peaks at integral masses, thus mass spectra can be used as envelope for a chord or a drum pattern take an interesting continuous curve like the Lorenz attractor (almost periodic, but with interim changes), consider it as a frequency control and quantize the curve values to pitches of a chord play a melody on a decaying sound using the frequency modulation on a pure tone (Osci.freqModFromSampledTone) use high frequency shape modulation in order to create new harmonics like in phase/frequency modulation distort a chorus saw oscillator or pure sine oscillator or sine with detuned harmonic with tanh, sine or zig-zag get the envelope of a piece of music and envelope a noise then cross-fade noise and music ring modulator with a chirp on a drum sequence quantized noise as amplitude or phase (stereo location) control of a string modulated quantization of a sinus signal allpass where you can choose the frequencies of 180° phase shift, the remaining degrees of freedom can be used to minimize the coefficients, i.e. to stabilize the filter; this allpass can be used to shift some band notches according to some sinoid controllers Is this the same as sequencing 2nd order allpass phasers? Certainly not. using differential equations to form sounds, e.g. simulate a guitar string or retrieve equation parameters from sampled guitar sounds by least squares fitting, initial chaotic behaviour could be simulated by some non-linear term, to determine it plot the residue from the least-squares problem (i.e. y''+b*y'+a*y) against y and y' and check if there is a functional dependency or just an undefinable cloud a control curve where we alternate periodically between two sines a FM (string) sound where the modulator has constant frequency but the carrier has a slow frequency modulation (additional to the phase modulation) mix a saw sound from sines, a) modulate sines with random slightly differing frequencies, envelope the sines with envelopes that differ only slightly from constant volume b) probably even more useful for a glittering pad effect: a (saw) sound where the harmonics appear and disappear randomly c) if you modulate the frequency of a sound, the frequencies of the harmonics are multiplied by the modulation factor (multiplicative modulation), how does it sound if one modulates the frequency of the harmonics additively? (this is equivalent to frequency shifting) Karplus-Strong algorithm for a) guitar (one wave as initial conditions) b) drums (driving force: Noise.white with decaying amplitude) Fuzz-booster for such guitar sounds, soft-clipping with various grades of smoothness (cf. interpolation of shrinkage functions) restore the harmonics of a tone from a chord by solving a system of linear equations in the fourier space filter a sound with a varying filter mask the filter masks are results of non-linear operators like FM synthesis or wrapped sines echo/reverb where the feedback contains some random amplification which can soften (or emphasize) attacks of echos == Organization == currently interpolation of State signals for time stretching requires either recomputation of the values or storage in a list or storable vector. It would be more efficient, if the current interpolation node would be stored as state. This is especially of interest for ControlledProcess where constant interpolation just means, that the same value is used several times. For interpolation with a small number of nodes, cycling the nodes would be fast enough, for large sets of nodes we would need a ring buffer. signal type for piecewise constant signals as they are produced by MIDI controllers this is a more efficient kind of storage than lazy storable vectors consisting of pieces of constant valued blocks. It is an instance of the Cut and Signal.Read classes. newtype PiecewiseConstant y = PiecewiseConstant EventList.Relative.BodyTime Int y We already use it for efficient control of LLVM signal generators. causal dimensional processes unification with other processes It would be nice to write (f $: x) for f being a causal process or a general process. However it seems to be not easily possibly to design an interface which can be unified by a type class. Define a heterogenous list type in order to get rid of nested pairs. Causal.T (A :- B :- Empty) (C :- Empty) (Double :- Double :- Empty) (Double :- Empty) interpolation can be divided into more general steps: 1. converting a discrete signal to a continuous one 2. quantize that See however, how discrete re-quantization is interpreted in terms of interpolation, which is somehow recurrent. Since the representation as function is a bit inefficient when applied to large time values, there might be an irreversible shift function that shifts to the left and forgets everything before time 0. Practical problem with that approach: The interpolated signal might have to be lowpass filtered and this cannot be done one the continuous signal. We have to do it before conversion to the continuous signal depending on how dense the signal is quantized later. This contradicts to the separation, unfortunately. check uses of FusionList.toList, FusionList.fromList They are quick hacks to make things running on FusionLists that were implemented for lists. check uses of FusionList.scanL It can only hardly be fused and thus should be replaced by crochetL. three sequence types: infinite sequences using one constructor like (:) an implementation is in Data.Stream finite or infinite sequences as wrapper around common lists finite sequences as wrapper around common lists Is there another way to do this? three sequence classes: superclass for finite and infinite sequences class for all kinds of finite sequences class for all kinds of infinite sequences NonNeg.Int for position parameters in Signal.Generic.Cut et.al. Error handling Currently errors like unit mismatch and division by zero emit an error. This is ok for use of Haskell as Domain Specific Language. However for use in a GUI program, this is not acceptable. Since units can now nicely be handled by types, we can add visible exception handling into the functions that use OccasionallyScalar and friends. Unfortunately, we cannot check for errors before the actual signal processing, because this may mean to check infinitely many objects. Consider the concatenation of infinitely many signals, where one of the signals is corrupt. Thus we must check for errors while doing signal processing. This is a nightmare, since we hardly want to replace each Double by Either String Double. This would be much less efficient in a block-oriented implementation. Maybe we should manage a parallel list containing the lengths of non-faulty blocks. [Either String Int] (Right n) means, that the next n values of the signal are fine. (Left msg) means, the the current value is not available because of an error specified in 'msg. Error messages The error handlings should be more informative, e.g. using Physical.Expression, and named variables. The signal processors could attach names to their variables before doing computations. oscillator sampleRate frequency ... = rate = name "oscillator.frequency" frequency / name "oscillator.sampleRate" sampleRate However this way we cannot distinguish between several instances of the same signal processors. We could ask the user to provide individual names for every call of a signal processor. This is quite cumbersome. oscillator "LFO" 44100 440 A preprocessor could insert source locations as arguments for the signal processors. oscillator 44100 440 -> oscillator (7,5) (44100, (7,10)) (440, (7,15)) discrete and continuous signals It seems, that we do not need to distinguish between discrete and continuous signals, but instead we can handle everything using properly chosen units: variance :: (t -> v) -> t*v^2 (~ norm2(x)^2) scalarProduct :: (t -> a) -> (t -> b) -> t*a*b convolution :: (t -> a) -> (t -> b) -> (t -> t*a*b) fourierAnalysis :: (t -> v) -> (1/t -> t*v) fourierSynthesis :: (t -> v) -> (1/t -> t*v) energyVolumeTimeSpace :: (t -> v) -> v energyVolumeFrequencySpace :: (f -> v) -> v*f (1/t -> t*v) -> v noise :: t*v^2 -> (t -> v) zeroCrossings :: (t -> v) -> (t -> 1/t) The convolution unit means that implicit filter windows (universal filter, Moog lowpass, all pass etc.) must have unit (t -> 1/t). euclideanNormSquare = variance energyVolumeTimeSpace x = sqrt (euclideanNormSquare x / period x) energyVolumeFrequencySpace x = sqrt (euclideanNormSquare x * period x) period x = sampleRate (fourierAnalysis x) sampleRate x = period (fourierAnalysis x) euclideanNormSquare x = euclideanNormSquare (fourierAnalysis x) fourierAnalysis (x <*> y) = fourierAnalysis x * fourierAnalysis y fourierAnalysis (x * y) = fourierAnalysis x <*> fourierAnalysis y fourierSynthesis (x <*> y) = fourierSynthesis x * fourierSynthesis y fourierSynthesis (x * y) = fourierSynthesis x <*> fourierSynthesis y stretch k x <*> stretch k y = stretch k (x <*> y) We need to manage several properties via types: Signals with amplitude with amplitude is the common case Without volume: Gate signals (piecewise constant) of type [Bool] State signal (piecewise constant) of type (Enum a => [a]) control signals as inputs to mapExponential, mapLinear, envelope Pulse or trigger signals (consist of peaks) of type [Bool] Signals with beginning different from zero examples: distortion by a table, histogram Probably we do not need that too often, because absolute beginning times conflict with arrangement of infinitely many signals. We also do not know how to pad a signal outside the stored data, padding with zeros might be inappropriate in some cases. It is probably better to apply shift information to the operations that need it. Alternatively we provide an optional signal constructor and a type class to abstract that away (time-invariant processors). This constructor must be before sample rate, since only this way we can combine signals with different beginning without interpolation. It is however logically exchangeable with amplitude constructor. Since it mutually excludes cyclic signals, it may just an alternative constructor to the Straight signal constructor. Note however, that e.g. recursive filters are not time invariant, because you have to give an initial condition for a fixed time point and the signal might start before that. Cyclic signals (excludes signal with non-zero beginning) example: input (and output?) of Fourier analysis waveform for an oscillator possibly best implemented using an array finite, infinite, either-or finite: input for computation of DC offset, volume, histogram maybe table for a non-linear distoration (wave shaping) cyclic signals are automatically somehow finite one time direction, two time directions (zippers) with integrated sample rate or with a sample rate context These features must be combined very flexible SampleRateContext t t' (Amplitude y y' Signal yv) SampleRateContext t t' (Amplitude y y' ShiftedSignal yv) SampleRateContext t t' (Amplitude y y' ShiftedSignal Double -> ShiftedSignal Bool) append :: Amplitude y y' Signal yv -> Amplitude y y' Signal yv -> Amplitude y y' Signal yv amplify :: y' -> Amplitude y y' signal yv -> Amplitude y y' signal yv envelope :: Amplitude y y' signal yv -> Amplitude y y' signal yv -> Amplitude y y' signal yv convolutionDiscrete :: -- necessary? Amplitude y y' signal yv -> Amplitude y y' signal yv -> Amplitude y y' signal yv convolution :: SampleRateContext q q' (Amplitude q q' signal yv -> Amplitude q q' signal yv -> Amplitude q q' signal yv) resample :: Interpolation t yv -> FixedSampleRate t t' Signal yv -> SampleRateContext t t' (Signal yv) resampleShifted :: Interpolation t yv -> FixedSampleRate t t' ShiftedSignal yv -> SampleRateContext t t' (ShiftedSignal yv) histogramDiscrete :: Amplitude y y' signal y -> SampleRateContext y y' (ShiftedSignal Int) histogramContinuous :: FixedSampleRate q q' (Amplitude q q' signal) q -> SampleRateContext q q' (Amplitude q q' ShiftedSignal q) Fourier transform is special input sample rate is forced by output length output sample rate is forced by input length fourierAnalysis :: FixedSampleRate t t' (Amplitude y y' Cyclic) yv -> FixedSampleRate t t' (Amplitude y y' Cyclic) yv fourierSynthesis :: FixedSampleRate t t' (Amplitude y y' Cyclic) yv -> FixedSampleRate t t' (Amplitude y y' Cyclic) yv The alternative to stacking types are type classes. However we would still need concrete data types, that implement the necessary functionality. But maybe function interfaces could be simplified. amplify :: (HasAmplitude sig) => y -> sig y -> sig y Problem: Where to enter the type of the volume value? The concrete types can still be stacked types, but how to prevent overlapping instances? Another alternative to stacking types is one type with type parameters for all options. data Signal rate amplitude progress signal = Signal rate amplitude progress signal rate: RatePhantom s or Rate (DN.T u t) amplitude: Flat or Amplitude (DN.T v y) This is more consistent with Dimensional.Causal progress: Straight or Cyclic signal: State.Signal y, Storable.Signal y, Gate, Dirac, or others Advantages: no arbitrary order of constructors in the type stack no doubled constructors in the type stack no cumbersome wrapping and unwrapping simple type classes: "Flat" becomes a type class of the amplitude "Homogeneous" is turned into unification of input and output amplitude "RateIndependent" is turned into unification of input and output sample rate different abstractions can be mixed easily Disadvantages: adding new features, e.g. beginning different from zero, means adding a type parameter which breaks existing code however, application code may use type synonyms for common combinations Synthesizer.Filter: can Filter multi-parameter class be substituted by a set of Haskell 98 classes? Braucht man wirklich eine Multiparametertypklasse? Braucht man überhaupt eine Typklasse? Tut es auch die Übergabe von Funktionen? If the Filter.Composition type makes the filter structure explicit (e.g. type 'Parallel (NonRecursive mask x) y') then we can use the same filter description in a type safe manner both for modulated and static filter application and transfer function computation. But this would also make it harder to parametrize the number of cascade parts in an allpass or Moog cascade. How about a type class for arrows that allow implementation of filters in various ways? Methods could be: delay, amplify, mix Instances could be: discrete filter, transfer function Problems: How to cope with real coefficients in discrete filters and complex numbers in transfer functions? Delicate: Handling of Feedback First order lowpass discrete filter: y0 = k*y1 + (1-k)*x0 simultaneous linear equations for transfer function y0 = k*y1 + (1-k)*x0 y1 = y0*z /y0\ - /1-k 0 k\ /x0\ \y1/ - \ 0 z 0/ |y0| \y1/ eliminate y1 (y0) = (1-k k*z) /x0\ \y0/ /y0\ = /1-k k*z\ /x0\ \w0/ = \1-k k*z/ \w0/ eliminate w0 w0 = (1-k)*x0 + k*z*w0 (1-k*z)*w0 = (1-k)*x0 w0 = (1-k)/(1-k*z)*x0 (y0) = ((1-k) + k*z*(1-k)/(1-k*z)) (x0) = ((1-k) / (1-k*z)) (x0) == miscellaneous == Piano samples Review Jazkore, lyriks Haskore