Karya, built on Mon Jul 24 11:39:07 PDT 2017 (patch 33511aca01257b76b88de7c7a2763b7a965c084e)

Perform.Signal

Description

Sample values are doubles, which means each point in the signal is 8*2 bytes. The double resolution is overkill for the value, but float would be too small for time given the time stretching.

TODO split this into Float and Double versions since only Warp really needs Double. Or does Warp really need Double?

Synopsis

# types

data Signal y Source #

A Signal is a TimeVector.Unboxed Util.TimeVector of Y values, which are just Doubles. It takes a phantom type parameter to make the signal's intended uses a little clearer. There are type aliases for the various flavors of signal below, but it really is just documentation and anyone who wants to operate on a generic signal can take a Signal y.

Instances

 # MethodsformatList :: [ControlRef] -> Doc Source # # This can only represent constant signals, since there's no literal for an arbitrary signal. Non-constant signals will turn into a constant of whatever was at 0. Methods # Methods # Methods # Methods # Methods # Use a TypedFunction or Function instead of this. Methods # Methods Eq (Signal y) # Methods(==) :: Signal y -> Signal y -> Bool #(/=) :: Signal y -> Signal y -> Bool # # MethodsreadsPrec :: Int -> ReadS (Signal y) #readList :: ReadS [Signal y] # Show (Signal y) # MethodsshowsPrec :: Int -> Signal y -> ShowS #show :: Signal y -> String #showList :: [Signal y] -> ShowS # Monoid (Signal y) # Methodsmappend :: Signal y -> Signal y -> Signal y #mconcat :: [Signal y] -> Signal y # # Signal.Control streams don't need sorted order. Methods # Methodsrnf :: Signal y -> () # # Methodspretty :: Signal y -> Text Source #format :: Signal y -> Doc Source #formatList :: [Signal y] -> Doc Source # # Methods

type Y = Double Source #

x_to_y :: X -> Y Source #

y_to_x :: Y -> X Source #

Some control signals may be interpreted as score time.

Signal composition, used by warps, is really tricky without a constant srate. Since integrate is the way to generate Warps, ensure they are at this srate by passing this to integrate.

1. 05 = 50 Hz = 800b/sec = 47kb/min
2. 01 = 100 Hz = 1600b/sec = 94kb/min

# constants

A pitch that shouldn't be played. Used for a non-existent pitch or one that goes out of the range of its scale.

This actually has to be 0 because that's also what at returns before the first sample.

type Tempo = Signal TempoSig Source #

A tempo is a normal Control signal, except that instead of going into the control map, it gets turned into a Warp and goes into the warp map.

type Warp = Signal WarpSig Source #

A tempo warp maps score time to real time. Of course the type is still (ScoreTime, Y), so functions that process Warps have to convert.

type Control = Signal ControlSig Source #

This is the type of performer-interpreted controls that go into the event's control map.

type NoteNumber = Signal NoteNumberSig Source #

This is the type of pitch signals used by the performer, after the scale has been factored out.

type Display = Signal DisplaySig Source #

This is the type of signals which are sent to the UI for display.

# construction / deconstruction

signal :: [(X, Y)] -> Signal y Source #

unsignal :: Signal y -> [(X, Y)] Source #

The inverse of the signal function.

set :: Maybe Y -> X -> Y -> Signal y Source #

Set the signal value, with a discontinuity.

unfoldr :: (state -> Maybe ((X, Y), state)) -> state -> Signal y Source #

coerce :: Signal y1 -> Signal y2 Source #

Sometimes signal types need to be converted.

with_ptr :: Display -> (Ptr (Sample Double) -> Int -> IO a) -> IO a Source #

# check

check_warp :: Warp -> [String] Source #

Find places where the Warp is non monotonically nondecreasing.

# access

at :: X -> Signal y -> Y Source #

sample_at :: X -> Signal y -> Maybe (X, Y) Source #

Find the value immediately before the point.

at_linear :: X -> Signal y -> Y Source #

This is a version of at_linear that extends the signal on either side with a straight line. A signal with no samples is a 1:1 line, one with a single sample is a 1:1 line passing through that point, and one with more samples will be extended according to the slope of the two samples at the beginning and end.

This is used by warp_pos.

Find the X at which the signal will attain the given Y. Assumes Y is non-decreasing. This should be the inverse of at_linear.

Unlike the other signal functions, this takes a single Y instead of a signal, and as a RealTime. This is because it's used by the play monitor for the inverse tempo map, and the play monitor polls on intervals defined by IO latency, so even when signals are lazy it would be impossible to generate the input signal without unsafeInterleaveIO. If I really want to pass a signal, I could pass regular samples and let the monitor interpolate.

This is like inverse_at, except that if the Y value is past the end of the signal, it extends the signal as far as necessary. When used for warp composition or unwarping, this means that the parent warp is too small for the child. Normally this shouldn't happen, but if it does it's sometimes better to make something up than crash.

The rules for extension are the same as at_linear_extend, and this function should be the inverse of that one. This ensures that if you warp and then unwarp a time, you get your original time back.

Just if the signal is constant.

head :: Signal y -> Maybe (X, Y) Source #

last :: Signal y -> Maybe (X, Y) Source #

uncons :: Signal y -> Maybe ((X, Y), Signal y) Source #

data Sample y Source #

Constructors

 Sample Fieldssx :: !X sy :: !y

Instances

 Eq y => Eq (Sample y) # Methods(==) :: Sample y -> Sample y -> Bool #(/=) :: Sample y -> Sample y -> Bool # Read.Read y => Read.Read (Sample y) # MethodsreadsPrec :: Int -> ReadS (Sample y) #readList :: ReadS [Sample y] # Show y => Show (Sample y) # MethodsshowsPrec :: Int -> Sample y -> ShowS #show :: Sample y -> String #showList :: [Sample y] -> ShowS # # MethodspeekElemOff :: Ptr (Sample Double) -> Int -> IO (Sample Double) #pokeElemOff :: Ptr (Sample Double) -> Int -> Sample Double -> IO () #peekByteOff :: Ptr b -> Int -> IO (Sample Double) #pokeByteOff :: Ptr b -> Int -> Sample Double -> IO () #peek :: Ptr (Sample Double) -> IO (Sample Double) #poke :: Ptr (Sample Double) -> Sample Double -> IO () # # MethodstoJSONList :: [Sample Double] -> Value # # Methods # MethodspokeElemOff :: Ptr (Sample Double) -> Int -> Sample Double -> IO () Source #peekByteOff :: Ptr b -> Int -> IO (Sample Double) Source #pokeByteOff :: Ptr b -> Int -> Sample Double -> IO () Source #poke :: Ptr (Sample Double) -> Sample Double -> IO () Source # # Methods

# transformation

merge :: [Signal y] -> Signal y Source #

concat :: [Signal y] -> Signal y Source #

This is like merge, but directly concatenates the signals. It should be more efficient when you know the signals don't overlap.

scale :: Y -> Y -> Y Source #

## scalar transformation

scalar_max :: Y -> Signal y -> Signal y Source #

Clip signal to never go above or below the given value. Like sig_max and sig_min except the value is scalar.

scalar_min :: Y -> Signal y -> Signal y Source #

Clip signal to never go above or below the given value. Like sig_max and sig_min except the value is scalar.

clip_bounds :: Y -> Y -> Signal y -> (Signal y, [(X, X)]) Source #

Clip the signal's Y values to lie between (0, 1), inclusive. Return the half-open ranges during which the Y was out of range, if any.

shift :: X -> Signal y -> Signal y Source #

take :: Int -> Signal y -> Signal y Source #

drop :: Int -> Signal y -> Signal y Source #

within :: X -> X -> Signal y -> Signal y Source #

map_x :: (X -> X) -> Signal y -> Signal y Source #

map_y :: (Y -> Y) -> Signal y -> Signal y Source #

map_err :: (Sample Y -> Either err (Sample Y)) -> Signal y -> (Signal y, [err]) Source #

## special functions

Compose the first signal with the second.

Actually, only the X points from the first warp are used in the output, so the input signals must be at a constant sample rate. This is different from the variable sampling used all the other signals, but is compatible with the output of integrate.

It also means that the output will have length equal to that of the first argument. Since the second argument is likely the warp of a sub-block, it will be shorter, and hence not result in a warp that is too short for its score.

TODO That also implies there's wasted work when warp outside of the sub-block's range is calculated. Solutions to that are either to clip the output to the length of the second argument (but this will cause incorrect results if the sub-block wants RealTime outside its range), or, once again, to make signals lazy.

TODO Wait, what if the warps don't line up at 0? Does that happen?

This is like compose, but implements a kind of "semi-absolute" composition. The idea is that it's normal composition until the second signal has a slope of zero. Normally this would be a discontinuity, but is special cased to force the output to a 1/1 line. In effect, it's as if the flat segment were whatever slope is necessary to to generate a slope of 1 when composed with the first signal.

Integrate the signal.

Since the output will have more samples than the input, this needs a sampling rate. The sampling rate determines the resolution of the tempo track. So it can probably be fairly low resolution before having a noticeable impact.

The last sample of a signal is supposed to extend indefinitely, which means that the output of integrate should extend indefinitely at a constant slope. But since signals are strict, I can't have infinite signals. So this integrate will only be accurate up until the final sample of the tempo given, and it's up to the caller to ensure that this range is enough. To this end, extend_signal will ensure there's a sample at the end of the track.

Take a Control in RealTime and unwarp it back to ScoreTime. The only reason to do this is to display in the UI, so the return type is Display.

Previously, unwarp was called as Signal.unwarp (Score.warp_to_signal warp) control. This converts the entire warp, which is often large thanks to the sampling rate required by integrate, for the sake of unwarping control, which is often very small, thanks to track slicing, and does so again and again. Fusion should take care of making the warp conversion just as efficient as manually applying the shift and stretch, but presumably can't handle only inverting the part of the warp needed to unwarp the control, becasue the signal is strict.

Can the pitch signals share a channel within the given range?

Pitch is complicated. Like other controls, if the pitch curves are different they may not share a channel. However, if the pitch curves are integral transpositions of each other, and the transposition is not 0, they should share. Unless the overlap occurs during the decay of one or both notes, at which point 0 transposition is ok.

This is actually a MIDI notion, so it should normally go in Perform.Midi, but it fusses around with signal internals for efficiency.

This function will be confused by multiple samples at the same time, so don't do that.