Alex
—May 01, 2022
My original concept for this Hilbert curve experiment was to have a point move along a Hilbert curve, with different rows and columns representing different note values. It would maybe have 12 columns, for the 12 notes, or 8 for a given scale, and I was going to see what kind of music a Hilbert curve could generate on its own.
I may still do that, but I realised that what I'd made reminded me a lot of the classic game "snake", and a new idea came to me. The curve would essentially serve as an interesting path for a snake to follow, and then every time it ate a piece of food the note would change. I can easily position the food at correct spacings on the path, so that it can actually play music.
This was going to make the code a lot larger, so I decided to start splitting up my code, into classes to represent the snake and its food, and somewhere to stick all the utility functions.
The GameObject
class is the base for both Snake
and SnakeFood
, and just holds a hindex
(an integer representing a position along a Hilbert curve, counted from the first point in the top left) and functions that can turn that into coordinates.
class GameObject {int hindex;GameObject (int in_hindex) {this.hindex = in_hindex;}int get_location(int coord) {return (points[this.hindex][coord] + PADDING) * SCALE_FACTOR;}int get_x_location() {return get_location(0);}int get_y_location() {return get_location(1);}}
The Snake
class builds on this, adding a length
attribute and a draw()
method
class Snake extends GameObject {int length;Snake (int in_hindex) {super(in_hindex);this.length = 1;}void draw() {int end_point = min(hindex, (N*N) - 1);for (int i = end_point - this.length; i < end_point; i++) {line(cached_h2x(i, 0),cached_h2x(i, 1),cached_h2x(i+1, 0),cached_h2x(i+1, 1));}}}
And SnakeFood
brings in some of the music, taking a note
(A-G#) and an octave
(0-8), which are converted to a frequency using a handy helper function. There is also a boolean hidden
which is used when there are no more food to be eaten, so I just hide the last one for aesthetics sake.
class SnakeFood extends GameObject {float frequency;boolean hidden;SnakeFood (String note, int octave, int in_hindex) {super(in_hindex);this.frequency = getFrequency(note, octave);this.hidden = false;}void draw() {if (!hidden) {point(get_x_location(),get_y_location());}}}
The last class is Note
which is really just used as an intermediary between me and SnakeFood
, as it defines an easier interface to enter music in, allowing a duration
as opposed to a hindex
. Then in the setup
we convert one to the other.
class Note {String note;int octave;int duration;Note (String in_note, int in_octave, int in_duration) {this.note = in_note;this.octave = in_octave;this.duration = in_duration;}}
The last2bits
and hindex2xy
functions remain relatively unchanged, but refactored into the new utils.pde
file. There is also a helper for getting the cached results:
int cached_h2x(int hindex, int coord) {return (points[hindex][coord] + PADDING) * SCALE_FACTOR;}
Then there is a function to convert a note name and an octave to a frequency that the Processing sound
library can play
float getFrequency(String note, int octave) {float keyNumber = NOTES.indexOf(note);if (keyNumber < 3) {keyNumber = keyNumber + 12 + ((octave - 1) * 12) + 1;} else {keyNumber = keyNumber + ((octave - 1) * 12) + 1;}// Return frequency of notereturn 440 * pow(2, (keyNumber - 49) / 12);};
Now that everything is nicely split up, the main code file is much neater, and easier to work with. You can find the full code listing here
https://github.com/adnathanail/hilbert-curve/tree/master/hilbert_curve_2
All that was left to do was encode a song in my Note
format, for which I chose this copy of In the Hall of the Mountain King
Note[] SONG = {new Note("D", 3, 2), new Note("E", 3, 2), new Note("F", 3, 2), new Note("G", 3, 2),new Note("A", 3, 2), new Note("F", 3, 2), new Note("A", 3, 4),new Note("G#", 3, 2), new Note("E", 3, 2), new Note("G#", 3, 4),new Note("G", 3, 2), new Note("D#", 3, 2), new Note("G", 3, 4),new Note("D", 3, 2), new Note("E", 3, 2), new Note("F", 3, 2), new Note("G", 3, 2),new Note("A", 3, 2), new Note("F", 3, 2), new Note("A", 3, 2), new Note("D", 4, 2),new Note("C", 4, 2), new Note("A", 3, 2), new Note("F", 3, 2), new Note("A", 3, 2),new Note("C", 4, 8),};
And now, what you've all been waiting for, a demo:
As you can hear, it just keeps playing the last note forever. It also doesn't look incredible, and it is very laborious to convert music to this format. All this will be improved in part 3