# initialize 2d array using 12 tone algorithm with Rust

a rust exercise in initalizing a 12x12 array based on the algorithm for twelve tone matrix described here: https://www.instructables.com/Create-a-Twelve-Tone-melody-with-a-Twelve-Tone-Mat/

i copy pasted description from there into a comment but the post has pictures which may help you understand.

the code doesn't actually do anything beyond initializing the array so i'm left with looking for ideas on how this would be implemented in idiomatic rust. i was also hoping for something readable but still efficient and that creates a read only block of data. suggestions there would be welcome, i'm still new at this.

i saw multiple crates for initializing an array described here: https://www.joshmcguigan.com/blog/array-initialization-rust/ but i had trouble figuring out how to use an algorithm to initialize the array rather than some examples of repeated data or random numbers.

also i didn't like how i needed to special case row 0 with an if statement, which needs to be seeded by the user. the rest of the matrix depends on the values in that seed row. i would've liked to have used prior data in the matrix itself to build up the rest of the matrix, like in a dynamic programming sort of way. so i would've liked to have set the first 12 values, then set the next rows based on that row and some other prior values initialized.

i filled in the data by column because that was easier for me to follow the algorithm in the post but i think the initialization could also be done via row if you believe that is necessary for a more idiomatic solution but we'd need to access 3 prior values: up and to the left, up, and to the left i think in the matrix (i didn't try that to check and was worried i might make a mistake that way).

i didn't put much thought into selecting the Array2D crate. i googled and the column wise initialization with a function seemed to fit the algorithm. maybe there is a better crate.

i think using this crate this way results in multiple assignments and clones in memory. i would've preferred just setting each element once in a read only matrix as it is calculated but in a column wise order. however, the readme of the crate suggested using a vector of vectors might be less efficient. i believe underneath the Array2D uses a single vector.

use array2d::Array2D;

/*
This Instructable demonstrates the procedure for composing twelve-tone melodies using a twelve-tone matrix.

This technique was developed by Arnold Schoenberg in 1921, and its purpose is to compose music in which each of the twelve pitches are heard equally. This technique prevents the emphasis of any one note, thereby avoiding any sense of key or tonality.

After learning this technique, you will be able to quickly write melodies for your compositions without emphasizing any particular tonality. With practice, creating a Twelve-Tone matrix is a breeze, taking less than five minutes to complete.

To complete the matrix, you will need to be able to add and subtract numbers between 1 and 12. Writing melodies from this matrix will require a basic understanding of music notation.

Items needed for this task include a 12 by 12 grid (as shown below) and a pen or pencil. To write melodies from the matrix, you will need staff paper or software for writing music.

To hear what this type of music can sound like, follow this link for an example of Schoenberg's twelve-tone music. www.youtube.com/watch

Add TipAsk QuestionCommentDownload
Step 1: Write Numbers in the Top Row
Write each of the whole numbers from 1 through 12 across the top row of the grid such that each number appears exactly once.

The order of the numbers can be either completely arbitrary or carefully planned. The intervals between these numbers will become the number of half-steps between the pitches of your melody. With this knowledge, you can place the numbers in the first row of this matrix at predetermined intervals for musical effect. I have placed numbers in the top row, as shown.

Add TipAsk QuestionCommentDownload
Step 2: Populate the First Column
While the notes in the first row could have been written in any order you chose, the first column depends entirely upon the first row, so you should not select numbers for this column in the same manner as you selected numbers for the first row.

Begin by determining the difference between the first two elements of the top row. In the example below, the difference between the first two elements is -2, since 1 - 3 = -2.

The opposite of the difference between the first two elements of the top row should be the difference between the first two elements of the first column. For example, since the difference between the first two elements of the first row is -2, the difference between the first two elements of the first column should be +2. This is achieved by adding 2 to the first element. Since 3 + 2 = 5, the second element is 5.

Follow the same procedure for each subsequent set of adjacent elements. Continuing with the example below, observe that the difference between 9 and 1 is +8. Therefore, the difference between the second and third elements of the first column should be -8. Subtracting 8 from 5 yields - 3. Note that -3 is not between 1 and 12. Whenever you encounter a result that is not between 1 and 12, add or subtract 12 to that number as needed to make the result between 1 and 12. In this case, -3 + 12 = 9, so a 9 appears as the third element of the first column.

An image of the completed first row and first column of our example also appears below.

Check your work. One way to verify that you have not made any mistakes is to make sure that each whole number from 1 through 12 appears exactly one time in the first column.

Add TipAsk QuestionCommentDownload
Step 3: Fill in the Second Row
Once the first row and column are complete, the cells in the second row can be populated. Like the first column, the remaining cells are dependent upon the first row.

Determine the difference between the first elements of the first and second rows. Continuing with our example, we can see that the difference between the first two elements is 2, since 5 - 3 = 2. Fill in the remaining elements of the second row such that the difference between each second row element and the first row element immediately above it is the same as the difference you have just determined. In the example below, this means that the second element of the second row should be 3, because 1 + 2 = 3, and the third element should be 11, since 9 + 2 = 11.

As in the previous step, if you encounter a number less than 1 or greater than 12, add or subtract 12 as needed so that the number you write in the matrix is in the range from 1 through 12.

An image of the completed second row of our example is shown.

Check your work. Make sure that each number from 1 through 12 appears only once in the row. If this is not the case, then you have made a mistake.

Add TipAsk QuestionCommentDownload
Step 4: Fill in the Remaining Rows
The remaining rows should be completed in the same manner as the second row was populated.

Determine the difference between first element of the lowest row that is not completed and the first element of the row immediately above it. This difference should be replicated throughout the rest of the row. Returning to our example, we can observe that the difference between 9 and 5 is 4, so each element of the third row should be 4 greater than the element in the cell above it.

Each subsequent row of our example has been completed in this manner, and the resulting completed matrix appears below.

You can check your work once again by verifying that each number from 1 through 12 appears only once in each row and each column.

One final way to verify that the matrix is properly completed is to look along the diagonal that runs from the top left corner of the matrix to the bottom right corner. The number in each of these cells should be the same. In our example, this is the case; the number 3 appears in each cell of the diagonal.

Add TipAsk QuestionCommentDownload
Step 5: Translate the Numbers to Pitches
Translate the Numbers to Pitches
Now that the matrix is complete, you can select a few rows or colums and "translate" them into music. Each number corresponds to a specific pitch according to this list.

C                 1
C# / Db      2
D                 3
D# / Eb      4
E                 5
F                 6
F# / Gb      7
G                8
G# / Ab      9
A                 10
A# / Bb       11
B                 12

Select one or more rows or columns from the matrix and translate them to pitches. You can read the rows from left to right or from right to left, and the columns can be read from top to bottom or from bottom to top.

Returning to our example, the seventh row, read from left to right, was chosen for the first half of a melody. The tenth column, read from bottom to top, was chosen for the second half of the melody. The row and column are translated as follows.

Row 7:
10   8    4   12  11   1   3    2     7     5    6    9
A     G   D#  B   Bb  C   D   Db  Gb  E    F   Ab

Column 10:
11   2    3    1    6    5     7      9     8    4    12   10
Bb  C#  D   C    F    E   Gb   Ab   G   Eb    B     A

Add TipAsk QuestionCommentDownload
Step 6: Write Music!
Write Music!
Make a melody from the pitches. Make sure that you don't change the orders of the pitches, as changing the order of the pitches defeats the purpose creating the matrix.

Recall that the pitches generated in step 5 were as follows:

A G D# B Bb C D Db Gb E F Ab

Bb C# D C F E Gb Ab G Eb B A

I have completed our example by writing music from these pitches.

Pick your time signature, rhythms, and dynamics in any way you like. Note that using a key signature is not necessary because your melodies were not derived with any sense of key or tonality. 20th Century composers are known for writing specific dynamic, articulation, and tempo markings, so you can imitate this style by specifically spelling out these factors as well. I have done this in the example below.

Your finished melodies will be atonal and consistent with the methodologies employed by Arnold Schoenberg and other 20th Century composers.
*/
fn generate_matrix(seed_row: [i32; 12]) -> Array2D<i32> {
let mut counter = 0;
let mut last = 0;

let next = || {
let row = counter % 12;

let result = if row == 0 {
seed_row[counter / 12]
} else {
(last + seed_row[row - 1] - seed_row[row] + 12) % 12
};

last = result;
counter += 1;

if result == 0 {
12
}
else {
result
}
};

Array2D::filled_by_column_major(next, 12, 12)
}

fn main() {
const SEED_ROW: [i32; 12] = [3, 1, 9, 5, 4, 6, 8, 7, 12, 10, 11, 2];
let matrix: Array2D<i32> = generate_matrix(SEED_ROW);

// sample result [3, 1, 9, 5, 4, 6, 8, 7, 12, 10, 11, 2, 5, 3, 11, 7, 6, 8, 10, 9, 2, 12, 1, 4, 9, 7, 3, 11, 10, 12, 2, 1, 6, 4, 5, 8, 1, 11, 7, 3, 2, 4, 6, 5, 10, 8, 9, 12, 2, 12, 8, 4, 3, 5, 7, 6, 11, 9, 10, 1, 12, 10, 6, 2, 1, 3, 5, 4, 9, 7, 8, 11, 10, 8, 4, 12, 11, 1, 3, 2, 7, 5, 6, 9, 11, 9, 5, 1, 12, 2, 4, 3, 8, 6, 7, 10, 6, 4, 12, 8, 7, 9, 11, 10, 3, 1, 2, 5, 8, 6, 2, 10, 9, 11, 1, 12, 5, 3, 4, 7, 7, 5, 1, 9, 8, 10, 12, 11, 4, 2, 3, 6, 4, 2, 10, 6, 5, 7, 9, 8, 1, 11, 12, 3]

println!("{:?}", matrix)
}


## 1 Answer

First, I can't comment of the choice of array2d since I'm not familiar with the crate, it may be a good choice, but my recommendation is the ndarray crate.

You made use of internal iteration. Rust has external iteration, which may be more appropriate for your use. Further information on iteration is available in the answer: https://stackoverflow.com/a/224675/14715117

Since you pass a next function to the constructor, which does internal iteration, your branches are a necessity. With external iteration, we still don't build up data on top of prior data in the matrix itself, "in a dynamic programming sort of way". Still, code after such a change in iteration seems more readable to me, and certainly more idiomatic, and I hope it looks as such to you too.

Keep in mind that Rust forbids uninitialized data, or rather allows it only as an unsafe possibility. Still, we can initialize cells with 0. You may be able to solve the task in two steps. First step initializes the first row with seed_row and all other cells with 0. Second step fills these other cells:

for row in 1 .. 12 {
for col in 0 .. 12 {
// initialize array[(col, row)] ...
}
}


Nitpick: there is (x + 12) % 12 in the code, which may be replaced by x.rem_euclid(12) as a solution that does the same thing in one operation, not two.

The following code is my take on the problem. It provides an external-iterator-based solution for both array crates.

Hoping it helps.

use array2d::Array2D;
use ndarray::prelude::*;
use std::iter;

fn generate_matrix(seed_row: [i32; 12]) -> Array2D<i32> {
let mut counter = 0;
let mut last = 0;

let next = || {
let row = counter % 12;

let result = if row == 0 {
seed_row[counter / 12]
} else {
(last + seed_row[row - 1] - seed_row[row] + 12) % 12
};

last = result;
counter += 1;

if result == 0 {
12
}
else {
result
}
};

Array2D::filled_by_column_major(next, 12, 12)
}

fn generate_matrix_from_iter_row_major(seed_row: [i32; 12]) -> Array2D<i32> {
let iter = seed_row.iter().copied().flat_map(|seed| {
let first_cell_in_col = iter::once(seed);
let other_cells_in_col = (1 .. 12).scan(seed, |state, row| {
*state = (*state + seed_row[row - 1] - seed_row[row]).rem_euclid(12);
if *state == 0 {
Some(12)
} else {
Some(*state)
}
});
first_cell_in_col.chain(other_cells_in_col)
});

Array2D::from_iter_column_major(iter, 12, 12)
}

fn generate_ndarray_matrix(seed_row: [i32; 12]) -> Array2<i32> {
let content: Vec<_> = seed_row.iter().copied().flat_map(|seed|
iter::once(seed).chain((1 .. 12).scan(seed, |state, row| {
*state = (*state + seed_row[row - 1] - seed_row[row]).rem_euclid(12);
if *state == 0 {
Some(12)
} else {
Some(*state)
}
}))
).collect();
Array2::from_shape_vec(
(12, 12).strides((1, 12)),
content
).unwrap()
}

fn main() {
const SEED_ROW: [i32; 12] = [3, 1, 9, 5, 4, 6, 8, 7, 12, 10, 11, 2];
let matrix: Array2D<i32> = generate_matrix(SEED_ROW);
let second_matrix: Array2<i32> = generate_ndarray_matrix(SEED_ROW);
let third_matrix: Array2D<i32> = generate_matrix_from_iter_row_major(SEED_ROW);

println!("{:?}", second_matrix);
assert_eq!(matrix, third_matrix);
assert_eq!(second_matrix.into_raw_vec(), third_matrix.as_column_major());
}