/tinyletter

The Programs of the Week the Clocks Rolled Forward

This Week’s Program: Mar 14 - Mar 18

Last week I built a client to the Dark Sky Forecast API. This week I weaved that data into my song generation algorithm to influence the final composition.

f7b0c42120f029ccf73c8f9f6d2587ad7cef2e8a

I modify the function signature of gen-song to accept optional weather data using the variadic rest parameters form. I knew that I wanted this attribute to be optional to song generation, but it was a toss-up between this and using the multi-arity form.

I use that great Clojure destructuring to pull out the latitude, longitude, and daily summary from the body of the Forecast API response and send the latter to the gen-song function and use the former two data points as metadata on the S3 object.

80e77eceac8f5f0dc144c63cb5db3fbc89970204

Because I chose the variadic form, the weather symbol there will always be a list. Now, in the body of the function I have to handle the cases around lists: whether they’re empty or they’re lists of nothing. Even though there’s extra work involved versus making this function multi-arity, the API feels cleaner. Knowing that the weather attribute here ought to be a map, I do (apply merge weather) to flatten the list of maps into a single map. I could have done (first weather) but merging feels a bit nicer and produces the same type of result.

98242413de3e568db2977560b0bbf601951eca23

Here’s the fun part.

The first datum I pull out of the Forecast response is the day’s lunar phase. Forecast models this as a number between 0 and 1 representing the “percentage complete” of a lunar cycle. To extract this from the response I use

(or (some :moonPhase (:data today))
    (datagen/float))

The data portion of the response is another list of maps, so I use some to pull out the first :moonPhase. If for whatever reason this is nil (as in the case where I do not supply weather data to gen-song, I use the random number generator to supply a random floating point number.

After that, I convert that floating point number to an integer and send it to the new lunar-illumination function. This function accepts this phase number and determines how full the moon is at that particular phase. The hardest part about this week was figuring out the arithmetic necessary to do this conversion. A full moon is 50, so I subtract 50 and take the absolute value of the result. This is the distance of the current phase from 50%. I then divide this by 10 and round to get a number between 0 and 5 inclusive, where 0 is the full moon at its brightest. This just so happens to correspond to the number of indices of the tempo-map. I use this as an index into that tempo-map with this (slightly reorganized) code:

(nth (reverse (keys tempo-map)) (lunar-illumination lunar-phase))

The more full the moon is, the faster the song will be.

4c0f83ef2fd77bcc312537aadc704d2431cab996

I could have left it at that, but this API didn’t sit right with me. A scale of 0 to 5 with 0 representing the high end seemed backward. It was backward: I had to reverse the keys of the tempo map in order for this to make sense. In this commit I subtract 50 and take the absolute value again to reverse the direction of the scale. Now 5 represents full illumination and that feels better.

ba0f6f07029db527367f2e33036066c1187c3894

Here’s a commit that’s just for funsies. I’ll walk through the code step-by-step.

(->> (range 0x1f311 0x1f319)
     (map int)
     (mapv (partial format "%c")))

The Unicode codepoint range 1f311 thru 1f318 are the Emoji characters for representing phases of the moon. I turn these into integers to weave through format "%c" which is a decent way to translate Unicode codepoints to strings.

I divide 100 (because lunar phase is a percentage) by the length of the emoji list minus 1 (7) to avoid an off-by-one error and then divide the lunar phase number by this.

Given a lunar phase (a number from 0 to 100), this function returns the emoji representing the moon at that phase.

(map lunar-str (range 100))

Shows off all the possible moons.

So now my song generation algorithm is influenced by the moon phase on the day that it’s run. The more illuminated the moon, the faster the song will be. Next week, I’ll work on incorporating more aspects of the day’s weather into the song: precipitation, temperature, length of day.

🌔 Mark