Close

Music Box Rule

A project log for Over Engineered Jack-In-The-Box

What if a Jack-in-the-box was an IoT smart device? This project is an example of how to use Pico device shadows to solve IoT problems.

adam-burdettadam burdett 06/28/2017 at 23:060 Comments

Music Box Magic

So how does a music box work?

A music box has notes of a song encoded on a cylinder or strip of paper. Each pin on a cylinder, or hole in a paper strip, triggers a tooth of the comb to chime. This is how music box's make their music. I was inspired by Wintergatan video demonstrating his knowledge of the technology.

Music Box Rule

To simulate a music box in a rules, I decided( with the help of my coworkers ) to store a song as a list of notes. The crank raises events. Crank events are handled by getting a list of notes for the current beat and sending those midi notes to fluidsynth. The rule to do this is below.

rule playSong {
    select when testing playSong 
    foreach getNotes().append(null) setting (n)
        pre { 
            check = ent:event_count == 0 
        }
        if ( check && not n.isnull() ) then
            playSound:play(n)
        always{
            ent:current_beat := ( not check) => ent:current_beat | 
                ((ent:current_beat < ent:songs[ent:playlist[ent:current_song]]{"song"}.length() - 1) => ent:current_beat + 1 | 0) on final;
            ent:event_count := (ent:event_count < ent:events_per_beat - 1) => ent:event_count + 1 | 0 on final;
            raise explicit event "openDoor"
                if (n == "O" && check)
        }
}

This rules name is "playSong". playSong selects on "testing playSong" event, which means this rules logic will only be evaluated when a "testing playSong" event is raised to the engine. Foreach is a way to loop a rule on a data set for a single event. This rule gets a list of notes to be played with getNotes function. getNotes function uses current beat variable to return a list of notes. So for every note that needs to be played this rule will fire. I append(null) to every list for the case that a list is empty, which would result in the rule not firing if null was not appended. "event_count" is used to gear down events. Some songs play faster than others, to handle this I discard some events with event_count. event_count is updatable per song. playSound:play(n) is the midi module I created that sends notes to fluidsynth.

Always is the postlude of this rule. I update current_beat and event_count here. With the use of a guard statement I check for a made up midi note "O". O is added to a song to signify when the latch on the door should be triggered open. When the latch should be open an event is raised where a different rule can catch and handle that event.

Discussions