CS 314 Assignment 9: Memos and Music

Due: April 24, 2015.

Some functions that you may need are provided in the file
Cons.java, and you will need some of your functions from the
last assignment. Question 1 may be done in Java or in Lisp; the rest
should be done in Java.

Write a recursive function Object solve(Cons e, String v) that
solves the equation e for variable v,
which we assume occurs at most once in e.

This function does the same thing as the earlier version of
solve; in this version, use patterns to rewrite the given
expression. The base cases are the same as before, and you
can copy your code for those cases.

If the left-hand side (lhs) of e is v, return e.

(solve '(= f (* m a)) 'f) => (= f (* m a))

If the right-hand side (rhs) of e is v, return e
with its arguments reversed.

(solve '(= (* m a) f) 'f) => (= f (* m a))

If the rhs is not v and not a Cons, return null.

If the rhs of e is a Cons (i.e. an operation), try to
solve the equation by applying algebraic transformations from a list
of patterns, algpats. For each pattern in the list, try
to transform the expression e using the pattern.
If the transformation works (is not null), call solve
recursively on the transformed version of e; if the result of
solve is not null,
return that result. Otherwise, continue through the list of patterns.
If all patterns have been tried, return null.

An initial list of patterns is provided; add patterns to complete the set.

Write a method hashCode() for the class Cons.
The hashCode of a Cons should be the XOR of the
hashCode of the first times 17 and the
hashCode of the rest times 127.
The hashCode of null should be 0; use an
auxiliary function to make sure that you do not try to call
hashCode() on null, which would cause a runtime
exception.

Write a class Memoizer (in a file Memoizer.java)
that implements memoization of a function call. Memoization is a
technique for wrapping an expensive function with a memory structure
that remembers previously computed values of the function. If the
function is called again with the same argument value, it will be
cheaper to look up the saved value than to recompute it.

The class should be created using new Memoizer(Functor f)
where f is a functor that wraps the function associated
with the memoizer.

Use a HashMap within the Memoizer to associate
function values with argument values. You can Google "java hashmap"
to get documentation of a HashMap.

Memoizer should have a method Object call(Object x)
to perform the wrapped function call.
The call method should operate as follows:

First, call should look up the parameter x
in the HashMap; if it is present, return the value associated
with x (without calling the Functor).

An Austin company is making a digital piano, drum and percussion
synthesizer, and they have hired you to write the software. Since
musicians are not always good programmers, the input language
that is used to specify a music pattern needs to be simple. The
software needs to convert the specified pattern into a time
sequence of I/O commands to drive the synthesizer units.
Since music is a language, we expect that a music program will be
structured as a tree.

The synthesizer driver program is implemented as a
discrete-event simulator, based on a PriorityQueue.
You can Google "java priorityqueue"
to get documentation of a PriorityQueue.

A discrete-event simulator processes a sequence of events
which are scheduled at particular clock times. The
PriorityQueue is used to hold the pending events, with the
priority being the time at which an event is scheduled to occur.
In operation, the simulator removes the highest-priority
(smallest time value) event from the queue, sets the current time
to the time of the event, and executes it;
the event will typically perform some action, and it may schedule
other events for the future. The simulation stops if the queue
becomes empty.

We will assume that time values are integers. Events that occur
at the same integer time are considered to be simultaneous,
regardless of their order, since the computer is much faster
than the music output. A time duration of 1 represents a 1/16
note duration.

An Event has a scheduled .time() and an
.action(); this class is provided. An action is a list,
consisting of a command followed by parameters; usually there
is only one parameter, the length of time the command lasts.
An event is added to the queue by the call
addevent(PriorityQueue pq, Cons action, int time) .

Execution of a command
may emit an I/O command to the synthesizer. Write a function
execute(PriorityQueue pq, Cons act, int time)
that executes an action by emitting appropriate commands.

The function emit(instrument, time, duration) sends
a command to an instrument; the arguments are all int.
The instrument values are as follows:

4

[bass_drum]

boom

5

[acoustic_snare]

snare

6

[pedal_hi_hat]

hat

7

[crash_cymbal_1]

cymbal

8

[cowbell]

cowbell

9

[ride_bell]

bell

10

[hand_clap]

clap

11

[tambourine]

tambourine

Commands are as follows, with d representing the duration
of the action; usually a duration should be a power of 2,
except for rest.

(tambourine d) multiplies its duration by a random number,
obtained by calling random.nextDouble() ;
if the integer result is ≥ 2, it schedules tambourine again with
that amount as its duration, at the current time plus current duration
minus that amount. It emits a [tambourine] (11) command with a
duration of 2 immediately. (This random behavior simulates
a tambourine player who smokes way too much weed.)

(seq action1 ... actionn) schedules each of its actions
to execute in sequence, one after the other. seq thus makes a sequence
of multiple actions look like a single action. seq could either
schedule all of its actions at once, starting with the current time
and adding the time of each action, or it could schedule the first
action and schedule a seq for the rest of the actions.

(sync action1 ... actionn) schedules each of its actions
for the current time. sync
represents synchronized execution of multiple subprograms.

(repeat n action) schedules n events with the specified
action. The first of these n events will happen at the same time
that repeat executes (i.e. simultaneously with repeat). One way to
accomplish this is to schedule the action immediately;
then if n > 1, schedule (repeat n-1 action) for the current
time plus the time of the action. Alternatively, repeat could simply
schedule all n events at once; this would use more queue space if n
is large.

(piano voice note d) emits a piano command using
the function emitp(voice, note, time, d).
A note is a string such as "C". A note may specify
an octave, such as "C5", and the note letter may be followed
by "#" or "b"
for sharp and flat. voice is an integer from 0 to 3.

Write a function int totaltime(Cons action) that calculates
the total time required for an action. For most actions, the time is
simply the duration of the action. A repeat action has a time
that is the repeat count times the time of the sub-action.
A seq action takes the sum of the times of its sub-actions.
A sync action takes the maximum time of its sub-actions.

A round is "a musical composition in which two or more
voices sing exactly the same melody" [Wikipedia]. Write a function
Cons round( Cons melody ) that will take a music program
and make a round out of it. We will assume that a round is formed
by repeating the same melody with three different voices; the voice
is indicated in the input melody by the variable ?i and
should be replaced by 0, 1, and 2 to form the three voices.
The second voice is delayed by 1/4 the length of the melody,
and the third voice is delayed by 1/2 the length of the melody.

The jFugue system,
http://jfugue.org,
allows sounds
to be produced from Java. An interface has been written to
allow the output of your program to produce sounds through
jFugue. Your program will not be any different whether you use
jFugue or not; using jFugue may make debugging easier because
you can hear whether the music sounds right.
If you would like to use jFugue, download it from
the class directory or the jFugue site and install it in the same
directory that you use for your program. Probably the easiest way
to install jFugue is to download the .jar file and unpack
it in the same directory with your program using

jar xf jfugue-4.0.3.jar

Use the file Cons.jfugue.java and rename it to be Cons.java .

mariagedamour.java is a file by a student that encodes the song
Mariage d'amour ; this file can optionally be added inside the
main() if you would like a longer piano piece. Compare to
Richard Clayderman's
version.