concertina

Elecronic concertina
git clone https://git.woozle.org/neale/concertina.git

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, &current_name, &current_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, &current_name, &current_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}