The Song Type
The Song type is the top level data structure containing all elements required to define a song. Song is pure data — it holds no mutable state and has no side effects.
case class Song(
title: Title,
tempo: Tempo = Tempo(120),
swing: Swing = Swing(0),
mixer: Mixer
)
The title, tempo and swing, should be self explanatory and take provided types wrapping a string and integers for these values.
NOTE: Swing is yet to be implemented.
The Mixer Type
Mixer is where we will place all our Tracks and this will simulate the effect of a real world mixing desk, where we can simultaneously play and manipulate audio on multiple tracks.
case class Mixer(tracks: NonEmptyList[Track[?]])
NOTE: The NonEmptyList type is a type from the cats library which ensures that the list is never empty.
The Track Type
Tracks in turn, are where we will place:
- The musical events we want to play.
- The instruments we want to play those events with.
- The playback mode —
Playback.Loopfor continuously looping tracks (e.g. drums) orPlayback.OneShotfor tracks that play once and stop (e.g. arranged parts). - TODO: Any insert FX we want to apply to the track. Insert FX
- TODO: Any send FX we want to apply to the track. Send FX
case class Track[Settings](
title: Title,
musicalEvent: MusicalEvent,
instrument: Instrument[Settings],
playback: Playback,
customSettings: Option[Settings] = None,
insertFX: List[FX] = List.empty,
sendFX: List[FX] = List.empty)
Playing a Song with the Sequencer
Song is pure data — to play it, wrap it in a Ref[IO, Song] and pass it to a Sequencer:
for
songRef <- Ref.of[IO, Song](mySong)
sequencer <- Sequencer(songRef)
_ <- sequencer.play()
yield ()
The Ref enables real-time updates to any song property while it's playing:
sequencer.updateSong(_.copy(tempo = Tempo(140)))