(require rsound)
and press enter. This should load the sound library.
If this command completes with no errors, proceed to the next step. If you get red error messages here, try re-downloading the rsound library,
or re-installing DrRacket and re-downloading the library.
(play ding)
in the lower half of the DrRacket window.
You should hear a "ding" sound. If you do, this confirms your sound library is installed correctly and is working!
(require rsound)
and
(play ding)
.
make-recursive-stream
that serves as a general-purpose stream-creation
function. This function will create an infinite stream where the next element of the stream is always calculated from
the previous element.
make-recursive-stream
takes two arguments: a function of one argument called f
, and an
initial value for the stream called init
. make-recursive-stream
returns a stream consisting
of the elements init
, (f init)
, (f (f init))
, and so on.
Example: (make-recursive-stream (lambda (x) (+ x 1)) 1)
==> stream of 1, 2, 3, 4, ...
Example: (make-recursive-stream (lambda (s) (string-append s "a")) "")
==> stream of "", "a", "aa", "aaa", ...
Hint: This function might seem more complicated than it really is. Just think about the code that creates an infinite stream of integers:
(define (integers-from n)
(stream-cons n (integers-from (+ n 1))))
Think about how you would generalize this.
1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 . . . . . etc . . . . . .
One way to define the triangle is to say that the first two rows of the triangle consist of one 1 and two 1's, respectively. Each further row is begins and ends with a 1, and each "interior" number in a row is the sum of the two numbers in the preceding row that are to the left and right of the number in question. For example, the 2 in the third row is the sum of the two 1's in the preceding row.
Define an infinite stream called pascal
where each item in the stream is a row of
Pascal's triangle, represented as a list. (In other words, you are defining an infinite stream
of finite lists.) Hint: Use
make-recursive-stream
by defining a function that takes a row of Pascal's triangle and
generates the next row.
Do not do this problem using the binomial theorem or using some other definition of Pascal's triangle.
Example: (define pascal . . . whatever you decide . . . )
(stream-enumerate pascal 6)
==> '((1) (1 1) (1 2 1) (1 3 3 1) (1 4 6 4 1) (1 5 10 10 5 1))
stream-flatten
that takes an infinite stream of lists and returns a new
stream consisting of all the elements from the first list, followed by all the elements from the
second list, and so on.
Example: (This assumes you've already written the Pascal's triangle part from above.)
(define flatpascal (stream-flatten pascal))
(stream-enumerate flatpascal 20)
==> '(1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5)
stream-merge
that takes two infinite streams of numbers where each individual
stream is in sorted order. stream-merge
returns a new stream consisting of all the items from the
two streams combined in sorted order. If there are duplicate numbers, they all should be kept in the merged stream.
Example:
(define pow2 (make-recursive-stream (lambda (x) (* x 2)) 1))
(define pow3 (make-recursive-stream (lambda (x) (* x 3)) 1))
(stream-enumerate (stream-merge pow2 pow3) 20)
==> '(1 1 2 3 4 8 9 16 27 32 64 81 128 243 256 512 729 1024 2048 2187)
A little about how sound generation works:
Sounds are transmitted through the air (or other mediums) via sound waves. The sound for a "pure" musical tone can be represented by a sine wave; that is, a sine function with a specific amplitude and period. In sound, the amplitude of a wave controls how loud the sound is (larger amplitudes lead to higher volumes) and the period of the wave (how fast the sine wave oscillates between the maximum and minimum values) controls the pitch of the tone (faster oscillations lead to higher pitches).
As an example, a sine wave consisting of 440 oscillations per second (Hertz) produces the musical note A (specifically, the A above middle C).
Because it is very hard to represent more sophisticated sounds (like speech) with sine waves, computers represent sound by sampling sound waves many times per second to try to approximate the true sound wave as closely as possible. For instance, CDs use an audio format that samples the sound wave 44,100 times every second.
You will be producing streams that represent sounds using this same sampling procedure. There is a sound library that comes with Racket that will let you listen to your streams as sounds. The only two functions that you need to know about are:
(play-stream stream)
is a function that takes an infinite stream and plays the stream as a
sound. It assumes that the stream consists of floating point numbers between -1 and 1 that
represent a sound wave sampled 44,100 times per second. In other words, playing one second
of sound uses the first 44,100 numbers from the stream.
I would recommend restricting your amplitude values to between about +0.2 and -0.2 because +1 and -1 correspond to the loudest possible sound your computer can make. So keep your volume low to prevent blowing out your speakers.
(stop)
is a function that stops all sounds playing. You will need to call this because when
you play an infinite stream, it naturally plays forever.
make-sine-stream
that takes a frequency as an argument. This function
returns an infinite stream of floating point numbers representing a sine wave that ranges
between -0.2 and +0.2 that has a period of 1/freq, and is sampled 44,100 times per second.
As an example, say we call (define s (make-sine-stream 441))
. This gives us a stream of
floating point numbers representing a sine wave that oscillates 441 times per second, sampled
at 44,100 times per second. The first 100 numbers in the sequence will therefore represent
one cycle of the wave.
Note that the sin
function in Racket works in radians.
Example:
(define s (make-sine-stream 441)) (stream-ref s 0) ==> 0 (stream-ref s 25) ==> 0.2 (stream-ref s 50) ==> 0 (stream-ref s 75) ==> -0.2 (stream-ref s 100) ==> 0 (play-stream s) ; plays the tone
make-sine-stream
returns a stream that wastes a lot of memory because the sine function
is periodic but your solution to the previous problem doesn't taking advantage of
that. In other words, your function for question 5 creates an infinite number of cons
cells, which is wasteful because the the infinite stream you are creating eventually repeats
the sequence of floating point numbers over and over again. It would be a lot more memory
efficient (constant memory versus infinite memory) if we could create a stream that reused
cons cells to create a true "circular" stream in memory, rather than having to continuously
allocate new cons cells to repeat the same sequence over and over.
This is analogous to the two ways we looked at to create an infinite stream of 1s: one way actually made a recursively-linked list with one cons cell, and the other way made a linked list of lots of cons cells.
What you need to do: Define a new function called make-circular-stream that takes a single list argument. What this function will do is create a circular stream consisting of all the items in the list, such that the cdr of the last element in the stream is a promise to point back to the first cons cell in the stream.
This function is tricky to get right because you have to save the first cons cell in the stream that you create with stream-cons so that when you reach the end of the list, you can point back to it. You can use mutation to solve this problem.
Here's how you'll know you did it right. The lines of code below create two infinite streams, each one alternating between 1 and -1. However, you can see by the output below that the "good" one is circular, and the "bad" one is not. (Note how the output for the good one labels the whole stream as "#0" and then the last cons cell contains a promise in the cdr that references #0 again, so the whole thing is circular.)
> (define good (make-circular-stream '(1 -1))) > (define bad (make-recursive-stream (lambda (x) (- x)) 1)) > (stream-enumerate good 4) '(1 -1 1 -1) > (stream-enumerate bad 4) '(1 -1 1 -1) > good #0='(1 . #<promise!(-1 . #<promise!#0#>)>) > bad '(1 . #<promise!(-1 . #<promise!(1 . #<promise!(-1 . #<promise!(1 . #<promise:...>)>)>)>)>)
Create a function that will create a stream for an arbitrary song specified in notes or pitches (worth 10 bonus points). For instance:
(define yankee-doodle (make-song-stream '(c4 c4 d4 e4 c4 e4 d4 g3 c4 c4 d4 e4 c4 c4 b3 b3 c4 c4 d4 e4 f4 e4 d4 c4 b3 g3 a3 b3 c4 c4 c4 c4)))
(play-stream yankee-doodle)
should play Yankee Doodle.
You can choose your own notation for the notes or pitches of the song. In my notation above, each note is a pitch and an octave. So c4 means middle C, and c5 would be one octave above. Octaves run C to C, so b3 is the B below middle C. You can use a different notation if you want; anything is fine (as long as Racket can read it). For instance, in my notation above, there is no notion of how long a note lasts (everything is in quarter notes). So you may want to add in note durations if you want more sophisticated songs.