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/README.md
2024-12-11 03:39:16 -08:00

60 lines
4.8 KiB
Markdown

## 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](https://github.com/pdx-cs-sound/popgen) 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](https://rustup.rs/)
## Run
```bash
# Basic
cargo run
# See help
cargo run -- --help
# Example with flags
cargo run -- -b 80 --root C[2]
```
## View Source
[popgen.rs](./src/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](https://crates.io/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](https://docs.python.org/3/library/argparse.html) 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](https://crates.io/crates/rand): Picking the sequence of notes requires some randomness
* [regex](https://crates.io/crates/regex): The go-to for regex, validating and parsing the note from the command line arguments
* [rodio](https://crates.io/crates/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](https://crates.io/crates/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)
[out0.wav](out0.wav)