.. | ||
src | ||
Cargo.toml | ||
out0.wav | ||
out1.wav | ||
README.md |
Background
Like many, I also watched the 4 Chords music video from the The Axis of Awesome on youtube roughly a decade ago. At the time, it provided a possible explination on why pop songs seemed somewhat unoriginal to me despite most of my peers enjoying pop. After that thought I resumed playing side two of The Wall and queued up Quadrophenia to play next.
Playing around with the provided popgen.py is both neat and addictive. I have decided that the focus of this portolio objective will be to reimpliment this program in rust, for the following reasons:
- Rust is more efficient, and it would be fun to run this on an embedded device which could be resource constrained.
- Rust is cool and it would serve me well to practice and keep up better with it
- Reimplementing the code forces me to essentially understand every line
- Despite being given explicit permission to do so with proper attribution, I prefer not to officially commit the code of another to my repository having a commit matching my name.
Setup
It is required to install the rustup rust toolchain
Run
# Basic
cargo run
# See help
cargo run -- --help
# Example with flags
cargo run -- -b 80 --root C[2]
View Source
Access outputs
TODO
Reflections, Results, Analysis
The effort involved for this portfolio objective was non trivial. I'll briefy discuss a some of the techniques and challenges involved specific to this implementation in rust. For a "how it works" perspective, I feel the comments copied over from python source and my few additions allow the code to be self documenting
External Crates:
- clap: In order to not drop any functionality from the original source, I wanted to ensure my application could handle all of the command-line argument as is done in popgen.py. I noticed there, argparse was doing some heaving lifting. I only briefy pondered rolling my own command line parsing, but then I remembered a past experience doing so with much simpler arguments. That made using
clap
a straightforward decision. - rand: Picking the sequence of notes requires some randomness
- regex: The go-to for regex, validating and parsing the note from the command line arguments
- rodio: Back in a portfolio objective rodio, cpal, portaudio-rs. I did a little research, and it seemed that rodio was the most abstract/easy to use, but still allowed me to manually set the sample rate as is necessary
Typing hell
The most tedious aspect of this objective was dealing with the typecasting needed for integer and float arithmetic. I won't discuss strong typing/rust arguments or any of that, but the crux of issues here came down to a few competing philosophies.
- I want to use the smallest integer units possible to handle the basic parameters. For example, a
u8
should easily contain thebpm
- Many parameters can be, and often are negative requiring the use of signed integers such as
melody_root
,position
, etc. On one hand, I did not realize this in time, so some rework was required. On the other hand, there is a desire to capture initial values as unsigned if that seems appropriate, and cast them only as needed for signed arithmetic (Example:chord_root
intobass_note
). - I want to use the highest precision possible floating point representation for the wave data samples which go to the buffer, to ensure the most accurate audio. I initially chose
f64
for this before realizing thatrodio
seems to only work withf32
. Aside from that, all sorts of casting was needed up from the base integer units. - Array indicies must be of type
usize
While the code here works it containers several occourences of riskyas
casting, and.try_into().unwrap()
which are not properly handled. As I have time, I will fix some of this stuff up.
Testing:
I brought in the provided unit tests for note_to_key_offset
and chord_to_note_offset
. These turned out to be rather useful for ensuring my functions of the same name worked correctly. I began adding some tests for make_notes
and may do the same for other functional units. I could move the tests into another source file, but decided not to, as to better parallel the source material.