Neale Pickett
·
2026-02-17
layouts.go
1// Package layouts parses text layouts into []Layout.
2//
3// Because Go doesn't appear to have any built-in mechanism to output Go source code literals,
4// it's either this or some intermediate format like JSON or gobs.
5// This bespoke package is pretty small compared to the alternatives.
6package layouts
7
8import (
9 _ "embed"
10 "strconv"
11 "strings"
12)
13
14//go:embed layouts.txt
15var layouts_txt string
16
17const (
18 Sides = 2
19 Rows = 4
20 Columns = 5
21)
22
23type ParseError struct {
24 line int
25 desc string
26}
27
28func (e *ParseError) Error() string {
29 return "line " + strconv.Itoa(e.line) + ": " + e.desc
30}
31
32type Layout struct {
33 Name string
34 Notes [Sides][Rows][Columns][2]int8
35}
36
37func ParseLayouts(s string) ([]*Layout, error) {
38 layouts := make([]*Layout, 0)
39 var current *Layout
40
41 row := 0
42 for lineno, line := range strings.Split(s, "\n") {
43 line = strings.TrimSpace(line)
44
45 // Skip blank lines and comments
46 if (line == "") || strings.HasPrefix(line, "#") {
47 continue
48 }
49
50 // Split the line up into fields
51 fields := strings.Fields(line)
52
53 // New layouts are anything other than 11-field lines with | in the middle
54 if (len(fields) != 11) || (fields[5] != "|") {
55 current = &Layout{
56 Name: line,
57 }
58 layouts = append(layouts, current)
59 row = 0
60 continue
61 }
62
63 if row >= Rows {
64 return nil, &ParseError{lineno, "too many rows: " + line}
65 }
66
67 // We can skip the middle | now
68 fields = append(fields[:5], fields[6:]...)
69 for col, button := range fields {
70 side := col / Columns
71 col = col % Columns
72 notes := strings.Split(button, "/")
73
74 // If only one note is listed, push and draw are the same
75 if len(notes) == 1 {
76 notes = append(notes, notes[0])
77 }
78
79 if len(notes) != 2 {
80 return nil, &ParseError{lineno, "invalid button entry"}
81 }
82
83 for i, note := range notes {
84 midi := ABC2MIDI(note)
85 if (midi == -1) && (note != "-") {
86 return nil, &ParseError{lineno, "unrecognized note: " + note}
87 }
88 current.Notes[side][row][col][i] = midi
89 }
90 }
91 row += 1
92 }
93
94 return layouts, nil
95}
96
97func DefaultLayouts() ([]*Layout, error) {
98 return ParseLayouts(layouts_txt)
99}