This repository has been archived on 2025-04-28. You can view files and clone it, but cannot push or open issues or pull requests.
computers-sound-music-portf.../code/popgen
2024-12-11 03:39:16 -08:00
..
src touches on popgen 2024-12-11 03:30:58 -08:00
Cargo.toml touches on popgen 2024-12-11 03:30:58 -08:00
out0.wav touches on popgen 2024-12-11 03:30:58 -08:00
out1.wav touches on popgen 2024-12-11 03:30:58 -08:00
README.md popgen readme update 2024-12-11 03:39:16 -08:00

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

popgen.rs

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
  • hound: This was recommened on a previous portfolio objective for saving wave files

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 the bpm
  • 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 into bass_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 that rodio seems to only work with f32. 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 risky as 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.

Results:

popgen.rs seems to work ok. I have done some testing with various root note inputs and other parameters, but not exhaustively. Similar to popgen.py, some note clicking is audible

Access outputs

Two output files were generated. I must have a slight bug in the save function, because these sound of somewhat lower quality.

out0.wav

out0.wav