Instantly share code, notes, and snippets.

Embed
What would you like to do?
Script that generates a set of speaker-test commands from a MIDI
import java.io.File
import com.sun.media.sound.StandardMidiFileReader
import javax.sound.midi.spi.MidiFileReader
import javax.sound.midi.{MidiSystem, ShortMessage, Track}
import scala.io.Source
object Main {
final val noteFunc =
"""note() {
| sleep `echo "$1" | bc -l`s
| ( speaker-test --frequency $2 --test sine )& pid=$!
| sleep `echo "$3" | bc -l`s
| kill -9 $pid
|}
""".stripMargin
final val noteOn = 0x90
final val noteOff = 0x80
def main(args: Array[String]): Unit = {
val sequence = new StandardMidiFileReader().getSequence(new File(args(0))
val msl = sequence.getMicrosecondLength
val tl = sequence.getTickLength
val spt = (msl / 1e6) / tl
val ns = sequence.getTracks.toVector.map(makeNoteSequence)
println(noteFunc ++ "\n\n" ++ ns.flatten.sorted.map(_.show(spt)).mkString(" &\n"))
}
type Instant = Long
type Duration = Long
type Pitch = Int
object Event {
final case class On(at: Instant, pitch: Pitch) extends Event
final case class Off(at: Instant, pitch: Pitch) extends Event
}
trait Event {
def at: Instant
def pitch: Pitch
}
final case class Note(start: Instant, duration: Duration, pitch: Pitch) {
def show(spt: Double): String = {
s"note ${start * spt} ${hertz(pitch)} ${duration * spt}"
}
}
implicit val noteOrdering: Ordering[Note] = (x: Note, y: Note) => (x.start - y.start).toInt
def notes(events: Vector[Event]): Vector[Note] =
events
.sortBy(_.at)
.foldLeft(State.empty)(_ ingest _)
.notes
private object State {
val empty = State(Map.empty, Vector.empty)
}
private final case class State(pressed: Map[Pitch, Instant], notes: Vector[Note]) {
def ingest(event: Event): State =
event match {
case Event.On(at, pitch) =>
// ignore duplicate note ons
if (pressed contains pitch) this
else copy(pressed = pressed + (pitch -> at))
case Event.Off(at, pitch) =>
pressed get pitch match {
// ignore missing note ons
case None => this
case Some(start) => copy(pressed = pressed - pitch, notes = notes :+ Note(start, at - start, pitch))
}
}
}
def makeNoteSequence(t: Track): Vector[Note] = {
val onOffMap = makeNoteStateMap(t)
notes(onOffMap)
}
def makeNoteStateMap(t: Track): Vector[Event] = {
(0 until t.size()).toVector.map(t.get).map(event => {
val evs = event.getTick
event.getMessage match {
case sm: ShortMessage =>
val key = sm.getData1
sm.getCommand match {
case `noteOn` => Some(Event.On(evs, key))
case `noteOff` => Some(Event.Off(evs, key))
case _ => None
}
case _ => None
}
}).filter(_.isDefined).map(_.get)
}
def hertz(key: Int): Double = {
Math.pow(2.0, (key - 69.0) / 12.0) * 440.0
}
}
@sorenbug

This comment has been minimized.

Copy link
Owner Author

sorenbug commented Feb 9, 2019

Much thanks to @ritschwumm for helping with the conversions from Events to Notes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment