Neale Pickett
·
2026-02-24
build.rs
1use std::env;
2use std::fs;
3use std::path::Path;
4
5fn abc_to_midi(note: &str) -> i8 {
6 if note == "-" || note.is_empty() { return -1; }
7
8 let mut chars = note.chars();
9 let first = chars.next().unwrap();
10
11 let mut val: i8 = match first {
12 'C' => 60, 'D' => 62, 'E' => 64, 'F' => 65, 'G' => 67, 'A' => 69, 'B' => 71,
13 'c' => 72, 'd' => 74, 'e' => 76, 'f' => 77, 'g' => 79, 'a' => 81, 'b' => 83,
14 _ => return -1,
15 };
16
17 for m in chars {
18 match m {
19 '♯' | '^' => val += 1,
20 '♭' | '_' => val -= 1,
21 '\'' => val += 12,
22 ',' => val -= 12,
23 _ => return -1,
24 }
25 }
26 val
27}
28
29fn main() {
30 let input = fs::read_to_string("src/layouts.txt").expect("Could not read layouts.txt");
31 let mut layouts_code = String::from("pub static LAYOUTS: &[Layout] = &[\n");
32
33 let mut current_notes = [[[[ -1i8; 2]; 5]; 4]; 2];
34 let mut current_name = String::new();
35 let mut row_idx = 0;
36
37 for line in input.lines() {
38 let line = line.trim();
39 if line.is_empty() || line.starts_with('#') { continue; }
40
41 let fields: Vec<&str> = line.split_whitespace().collect();
42
43 // Check if this is a header or a data row
44 if fields.len() != 11 || fields[5] != "|" {
45 // If we were processing a previous layout, close it
46 if !current_name.is_empty() {
47 write_layout(&mut layouts_code, ¤t_name, ¤t_notes);
48 }
49 current_name = line.to_string();
50 current_notes = [[[[ -1i8; 2]; 5]; 4]; 2];
51 row_idx = 0;
52 continue;
53 }
54
55 // Parse Row
56 let mut data_fields = fields.clone();
57 data_fields.remove(5); // Remove "|"
58
59 for (col_idx, button) in data_fields.iter().enumerate() {
60 let side = col_idx / 5;
61 let col = col_idx % 5;
62 let notes: Vec<&str> = button.split('/').collect();
63
64 let push = abc_to_midi(notes[0]);
65 let pull = if notes.len() > 1 { abc_to_midi(notes[1]) } else { push };
66
67 current_notes[side][row_idx][col][0] = push;
68 current_notes[side][row_idx][col][1] = pull;
69 }
70 row_idx += 1;
71 }
72
73 // Write the final layout
74 if !current_name.is_empty() {
75 write_layout(&mut layouts_code, ¤t_name, ¤t_notes);
76 }
77
78 layouts_code.push_str("];\n");
79
80 let out_dir = env::var("OUT_DIR").unwrap();
81 let dest_path = Path::new(&out_dir).join("layouts_data.rs");
82 fs::write(&dest_path, layouts_code).unwrap();
83
84 println!("cargo:rerun-if-changed=layouts.txt");
85 println!("cargo:rerun-if-changed=build.rs");
86}
87
88fn write_layout(code: &mut String, name: &str, notes: &[[[[i8; 2]; 5]; 4]; 2]) {
89 code.push_str(" Layout {\n");
90 code.push_str(&format!(" name: {:?},\n", name));
91 code.push_str(" notes: [\n");
92 for side in notes {
93 code.push_str(" [\n");
94 for row in side {
95 code.push_str(" [");
96 for col in row {
97 code.push_str(&format!("[{}, {}], ", col[0], col[1]));
98 }
99 code.push_str("],\n");
100 }
101 code.push_str(" ],\n");
102 }
103 code.push_str(" ],\n },\n");
104}