Skip to content
This repository has been archived by the owner on Apr 29, 2020. It is now read-only.

Factor out separate type for Score without rests? #1

Closed
hanshoglund opened this issue Mar 23, 2013 · 1 comment
Closed

Factor out separate type for Score without rests? #1

hanshoglund opened this issue Mar 23, 2013 · 1 comment

Comments

@hanshoglund
Copy link
Member

We now have the following semantics:

    type Track a = [(Time, a)]
    type Part a  = [(Duration, a)]
    type Score a = [(Time, Duration, Maybe a)]

I have a feeling that the Maybe may not be necessary for anything but implementing rest. Indeed, most Score instances seem to be juggling around it. On the other hand, a score without rests does not really seem like a musical score to me.

Note that we can remove all rests from a score and still get an unaffectd score as far as notes are concerned, however without them a note-less score can note does not have duration or offset, so we can not do

    c |> d |> rest^*2

Onset, offset, duration

This is related to the onset/offset/duration mixup. I am unsure what onset/offset/duration should mean for the different types.

The duration laws is a starting point:

    duration a = offset a - onset a
    duration a >= 0

accordingly

    offset a   >= onset a

Intutuion says that a |> b should place the onset of b at the offset of a, i.e.

    a |> b =  a <|> startAt (offset a) b
           =  a <|> delay (offset a - onset b) b

In the original library, onset was always zero, so we could simply do

    a |> b  =  a <|> delay (duration a) b
            =  a <|> delay (offset a - onset a)         -- as per the duration law
            =  a <|> delay (offset a - 0)
            =  a <|> delay (offset a - onset b)  

Instancces

Track and Score has more or less identical onset and offset instances:

    instance HasOnset (Track a) where
        onset x  = minimum (map on x)    where on  (t,x) = t
        offset x = maximum (map off x)   where off (t,x) = t

    instance HasOnset (Score a) where
        onset x  = minimum (map on x)    where on  (t,d,x) = t
        offset x = maximum (map off x)   where off (t,d,x) = t + d

This also give us duration instances for Track and Score per the duration laws.

    instance HasOnset a => HasDuration a where
        duration x = offset x - onset x

Note that in a track/score with onset zero (let us call that a normalized track/score), we still have duration = offset.

Part is different: duration is sum and there is no onset/offset.

    instance HasDuration (Part a) where
        duration x = sum (map duration x)

Note that while both tracks, scores and parts have scale, only tracks and scores have delay. Put it differently, parts are completely relative in time (like vectors), while scores and tracks are absolute (like points). Maybe we should rename parts to reflect this?

If a score is notes with possibly empty space around them, and onset, offset and duration is determined by the note occurences, then a score without values can not have a duration. Or well, it is error "empty list". Disambiguate as follows:

    duration mempty = 0
    onset mempty = 0
    offset mempty = 0

We want to have a function rest :: Score a, analogous to note :: a -> Score a. The purpose of a rest is simply to allow sequential catenation (juxtaposition) without values (similar to strut in diagrams). We can do rests either by wrapping score elements in Maybe, or by maintaining a separate duration valid if there are no elements. I choose the maybe option as it is more clear.

In the original library, I used an implementation like delay t x = rest^*t |> x. This does not make sense now, as rests are themselves elements (i.e. this implementation of delay would affect duration but not onset instead of the other way around.

Summary

Think of it like this:

  • Rests (Nothing) is not a way to encode empty space (as it is in a classical score).
  • Rather, each score (or note, track etc) has a nominal onset and offset. There may be sound outside these values (see pre-onset etc below), they are defined as logical start (attack action) and stop (damp action) time.
  • Redifine sequential and parallel composition to align so that onset a == onset b, or offset a == onset b respectively. For instaneous things, sequential composition is (of course) not defined.
  • Rests are simply empty scores for padding purposes. We remove them with removeRests.

Side notes

Diagrams

Compare this to (===) and (|||) in diagrams. Normally, all diagrams have an intuitive bounding box. However, we can also use functions like strut allow us to create empty diagrams with bounding boxes for juxtaposition purposes. I feel want something similar, and not just for Score (Maybe a), but the real thing.

Prepared notes

Think of something like an ADSR envelope. Logically, the onset of the note is the start of the attack phase. However, under parallel catenation we want the maximum level (in-between A and D) to be the concurring point. It makes sense to think of an ADSR as having five interesting events: pre-onset <= onset <= post-onset <= offset <= post-offset. Given onset and offset (or onset and duration), the pre and post events are determined by the qualities of the instrument: how long does it take to to excite, stabilize and tranquilize. We can thus amend our duration laws:

    excite      a  =  onset a      - preOnset a
    stabilize   a  =  postOnset a  - onset a
    tranquilize a  =  postOffset a - offset a

    excite, stabilize, tranquilize > 0
@hanshoglund
Copy link
Member Author

This was solved by issue #13. Note that now delay could be implemented as:

delay' t x = mcatMaybes $ rest^*t |> fmap Just x

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

1 participant