aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSan Jacobs2025-11-12 05:21:32 +0100
committerSan Jacobs2025-11-12 05:21:32 +0100
commit09ec5f18a0583c94286f1f1f1b462f675d93d94d (patch)
treed7162f3576e5e4fb93edd61f1bdf7f73235b3703
parent5929af19af3d74eececa97cc1bf305102614492b (diff)
downloadbetter-report-master.tar.gz
better-report-master.tar.bz2
better-report-master.zip
Starting to read wav metadata directlyHEADmaster
-rwxr-xr-xbuild.bat8
-rwxr-xr-xparts/end.html46
-rwxr-xr-xparts/start.html9
-rwxr-xr-xparts/start2.html103
-rw-r--r--[-rwxr-xr-x]src/main.odin (renamed from main.odin)1514
-rw-r--r--src/wav/wav.odin140
6 files changed, 902 insertions, 918 deletions
diff --git a/build.bat b/build.bat
index f847c6e..f8662d4 100755
--- a/build.bat
+++ b/build.bat
@@ -1,3 +1,5 @@
-odin build . -debug -pdb-name:bin/better-report.pdb -out:bin/better-report.exe
-bin\better-report.exe test\
-odin build . -o:speed -out:C:/tools/better-report.exe \ No newline at end of file
+@echo off
+rem odin build src/ -debug -pdb-name:bin/better-report.pdb -out:bin/better-report.exe
+rem bin\better-report.exe test\
+rem odin build src/ -o:speed -out:C:/tools/better-report.exe
+odin run src/wav \ No newline at end of file
diff --git a/parts/end.html b/parts/end.html
deleted file mode 100755
index cc51027..0000000
--- a/parts/end.html
+++ /dev/null
@@ -1,46 +0,0 @@
- </tbody>
- </table>
- </div>
-</body>
-
-<script type="text/javascript">
- const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent;
- prevColumnSorted = document.getElementsByClassName("current-sort")[0];
-
- const comparer = (idx, asc) => (a, b) => ((v1, v2) =>
- v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
- )(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
-
- function doSort(th) {
- console.log("Doing sort to: ", th.textContent, th);
-
- const table = th.closest('table');
- const tbody = table.querySelector('tbody');
- const columnIndex = Array.from(th.parentNode.children).indexOf(th);
-
- // Toggle sort direction
- if (prevColumnSorted == th) {
- this.asc = !this.asc;
- } else {
- this.asc = true;
- }
-
- // Get all rows, sort them, then reappend to tbody
- const rows = Array.from(tbody.querySelectorAll('tr'));
- const sortedRows = rows.sort(comparer(columnIndex, this.asc));
-
- sortedRows.forEach(row => tbody.appendChild(row));
- prevColumnSorted = th;
- }
-
- // Event listeners and initial sort
- document.querySelectorAll('th').forEach(th => th.addEventListener('click', () => {
- document.getElementsByClassName("current-sort")[0].classList.remove("current-sort");
- th.classList.add("current-sort");
- doSort(th);
- }));
-
- doSort(document.getElementsByClassName("current-sort")[0]);
-</script>
-
-</html>
diff --git a/parts/start.html b/parts/start.html
deleted file mode 100755
index a1e9809..0000000
--- a/parts/start.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-
-
-<head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
-
- <title> \ No newline at end of file
diff --git a/parts/start2.html b/parts/start2.html
deleted file mode 100755
index d7de36f..0000000
--- a/parts/start2.html
+++ /dev/null
@@ -1,103 +0,0 @@
-</title>
-
- <link rel="icon" href="/favicon.ico">
- <link rel="icon" href="/favicon.svg" type="image/svg+xml">
-
-
- <style>
- html {
- color: white;
- background: #141414;
- font-family: sans-serif;
- }
- td {
- padding: 3px 6px;
- background: #181818;
- border-radius: 3px;
- border: #ffffff0d solid 1px;
- }
- th {
- border-radius: 3px;
- padding: 5px 3px;
- cursor: pointer;
- font-weight: bold;
- }
- thead {
- top: 0;
- position: sticky;
- background: #141414;
- outline: #fff1 solid 1px 1px 1px 3px;
- }
- .main-container {
- width: max-content;
- display: block;
- margin: auto;
- }
- .header-tr {
- border-bottom: solid 1px #fff3;
- }
- .current-sort {
- background: #424;
- }
- .prelude {
- background: #ffffff0c;
- padding: 20px;
- margin-bottom: 5px;
- border-radius: 4px;
- display: flex;
- }
- .prelude p {
- margin: 2px 0px;
- }
- .prelude b {
-
- }
- svg {
- filter: invert();
- display: block;
- margin: auto;
- }
- h1 {
- font-size: 16px;
- margin-top: 3px;
- margin-bottom: 10px;
- }
- h6 {
- font-family: monospace;
- font-size: 10px;
- }
- a {
- color: #c0f;
- text-underline: 0px;
- }
- .logo-section {
- width: min-content;
- margin-right: 15px;
- }
- </style>
-
-
-</head>
-
-
-
-<body>
-
- <div class="main-container">
- <div class="prelude">
- <div class="logo-section">
- <a href="https://knektlyd.no/">
- <svg width="110px" viewBox="0 0 123 35" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
- <g id="_3a" serif:id="3a">
- <path id="path29537-1" d="M32.013,29.14L26.457,35.565L20.22,30.123L20.22,19.276L11.868,19.276L11.868,30.236L4.271,35.641L4.271,7.332L0,7.332L4.346,1.436L11.868,1.436L11.868,18.369L20.22,12.775L20.22,4.838L27.704,0L27.704,13.307L22.017,17.116L27.704,17.116L27.704,29.14L32.013,29.14Z" style="fill-rule:nonzero;"/>
- <path id="path29539-2" d="M57.827,29.14L53.783,35.565L47.282,29.972L47.282,15.118L40.554,15.118L40.554,29.14L44.561,29.14L40.554,35.565L34.016,29.972L34.016,11.906L40.517,8.655L40.517,14.287L46.224,8.655L53.783,14.287L53.783,29.14L57.827,29.14Z" style="fill-rule:nonzero;"/>
- <path id="path29541-7" d="M77.783,20.334L65.953,25.058L65.953,29.14L75.591,29.14L71.093,35.565L59.339,31.408L59.339,15.118L68.032,8.655L77.783,14.06L77.783,20.334ZM71.811,18.622L71.811,15.118L65.953,15.118L65.953,20.976L71.811,18.622Z" style="fill-rule:nonzero;"/>
- <path id="path29543-0" d="M104.503,29.178L100.459,35.641L93.883,30.009L93.883,21.543L87.609,21.543L93.883,15.118L87.231,15.118L87.231,29.14L91.275,29.14L87.231,35.565L80.768,29.972L80.768,3.25L87.231,0L87.231,14.287L91.464,10.091L100.459,10.091L94.827,15.836L100.459,20.712L100.459,29.178L104.503,29.178Z" style="fill-rule:nonzero;"/>
- <path id="path29545-9" d="M121.889,10.091L118.261,15.118L113.536,15.118L113.536,29.178L117.58,29.178L113.536,35.565L107.073,29.972L107.073,15.118L102.5,15.118L113.536,4.044L113.536,10.091L121.889,10.091Z" style="fill-rule:nonzero;"/>
- </g>
- </svg>
- </a>
- <h1 style="width:max-content;margin:auto;">Lydrapport</h1>
- <h6 style="width:max-content;margin:auto;">Version 0.9</h6>
- </div>
- <div class="info-section">
diff --git a/main.odin b/src/main.odin
index cd3d3c5..71ae6ed 100755..100644
--- a/main.odin
+++ b/src/main.odin
@@ -1,758 +1,758 @@
-package main
-
-import "core:fmt"
-import "core:os"
-import "core:os/os2"
-import "core:path/filepath"
-import "core:sys/windows"
-import "core:strings"
-
-/*
-TODO: Simplify pre-allocation. Just allocate a bunch. It's probably fine.
- Maybe do it by just counting how many lines are longer than 2 characters.
-TODO: Drag-n-drop window if no files are specified
-*/
-
-VERBOSE :: false
-INCLUDE_DATE :: false // By default I delete the retarded date field that says what day the report was generated.
-
-PART_ONE :: #load("parts/start.html", string)
-PART_TWO :: #load("parts/start2.html", string)
-PART_END :: #load("parts/end.html", string)
-
-Device :: enum {
- UNSET,
- ZOOM,
- SD6,
- SD8, // Tested with 888
-}
-
-Stages :: enum {
- TITLE,
- INFO,
- HEADER,
- BODY,
-}
-
-Info_Line :: struct {
- field : string,
- entry : string,
-}
-
-Report :: struct {
- title : string,
- info_lines : []Info_Line,
- header : []string,
- table : [][]string,
- column_count : int,
- row_count : int,
- info_line_count : int,
- tc_column_index : int,
-}
-
-main :: proc() {
- when ODIN_OS == .Windows {
- windows.SetConsoleOutputCP(windows.CODEPAGE.UTF8)
- }
-
- input_file_name : string
- if len(os.args) < 2 {
- fmt.println("ERROR: No file submitted.")
- return
- } else {
- fmt.printf("Input path: {}\n", os.args[1])
- input_file_name = os.args[1]
- }
-
-
- path_info, error := os.stat(input_file_name)
-
- file_count := 1
- files_done := 0
- if error == os.ERROR_NONE {
-
- file_list : [dynamic]string
- if(path_info.is_dir) {
- fmt.printf("Directory submitted! Walking directory...\n\n")
- fmt.printf("šŸ“ {}\n", path_info.name)
- try_os2 := walk_directory(path_info.fullpath, &file_count, &file_list, 1)
- if len(file_list) == 0 && try_os2 {
- fmt.printf("\nNot_Dir error encountered. Trying os2 version...\n\n")
- fmt.printf("šŸ“ {}\n", path_info.name)
- walk_directory_os2(path_info.fullpath, &file_count, &file_list, 1)
- }
- } else {
- fmt.println("File submitted! Processing file...")
- append(&file_list, strings.clone(path_info.fullpath))
- }
-
- for file, f in file_list {
-
- file_info, _ := os.stat(file)
- fmt.printf("\nšŸ“„ File {}: {}\n", f+1, file_info.name)
- parsed, ok_parse := parse(file_info.fullpath)
- if !ok_parse {
- fmt.printf("Parse failed: {}\n", file_info.fullpath)
- continue
- }
- output_name := fmt.aprintf("{}/{}_Knekt_Lydrapport.html", filepath.dir(file_info.fullpath), parsed.title, allocator=context.temp_allocator)
- render(parsed, output_name)
- free_all(context.temp_allocator)
- files_done += 1
- }
- fmt.printf("\nCompleted {}/{} files.\n\n", files_done, len(file_list))
- } else {
- fmt.printf("ERROR could not get path info for: {}\n", input_file_name)
- }
-
-}
-
-
-
-
-parse :: proc(path : string, device : Device = .UNSET) -> (Report, bool) {
- device := device
- output : Report = {}
- data, ok := os.read_entire_file(path, context.temp_allocator)
- if !ok {
- fmt.printf("ERROR: Could not read file: {}\n", path)
- return {}, false
- }
- file_info, _ := os.lstat(path, context.temp_allocator)
-
-
- lines := strings.split_lines(string(data), allocator=context.temp_allocator)
-
-
- // STAGE 1 --------------------------------------------------------------
- // First, we detect what kind of sound report this is
- for line, line_number in lines[:3] {
- if (device!=.UNSET) { break }
- if line == "\"SOUND REPORT\"," {
- device = .ZOOM
- if VERBOSE do fmt.printf("Detected ZOOM from quotes and comma on line index {}\n", line_number)
- }
- if line == "\"ZOOM F8\"," {
- device = .ZOOM
- if VERBOSE do fmt.printf("Detected ZOOM from \"ZOOM F8\" on line index {}\n", line_number)
- }
- if line == "SOUND REPORT" {
- device = .SD6
- if VERBOSE do fmt.printf("Detected SOUND_DEVICES from unquoted SOUND REPORT line index {}\n", line_number)
- }
- if len(line)<15 do continue
- if line[:13] == "SOUND REPORT," {
- device = .SD8
- if VERBOSE do fmt.printf("Detected SOUND_DEVICES 8-series from SOUND REPORT with missing newline on line index {}\n", line_number)
- }
- }
-
- if device == .UNSET {
- fmt.printf("ERROR: Unable to detect sound report type!\n")
- return {}, false
- }
-
-
-
- // STAGE 2 --------------------------------------------------------------
- // Measuring content for allocation
-
- switch device {
- case .ZOOM:
- output.column_count = 21 // Ugly magic number, could be fucked by firmware update
-
- // Padded for expanding info lines from unchanging columns
- output.info_lines = make([]Info_Line, 2+output.column_count, context.temp_allocator)
- output.info_line_count = 2
-
- output.row_count = strings.count(string(data), "\n") - 7 // Ugly magic number, could be fucked by firmware update
- output.table = make([][]string, output.row_count, context.temp_allocator)
- output.header = make([]string, output.column_count, context.temp_allocator)
- for &row in output.table {
- row = make([]string, output.column_count, context.temp_allocator)
- }
- case .SD6:
- second_to_last_line := lines[len(lines)-2]
- output.column_count = strings.count(second_to_last_line, ",")
- count_stage : Stages = .TITLE
- for line, l in lines {
- switch count_stage {
- case .TITLE:
- if l == 1 { // Ugly magic number, could be fucked by firmware update
- count_stage = .INFO
- }
- case .INFO:
- if line == "," {
- count_stage = .HEADER
- continue
- } else if len(line) > 2 {
- output.info_line_count += 1
- }
- case .HEADER:
- if line == "" {
- count_stage = .BODY
- }
- case .BODY:
- if len(line)>2 {
- output.row_count += 1
- }
- }
- }
- output.info_lines = make([]Info_Line, output.info_line_count+output.column_count, context.temp_allocator)
- output.header = make([]string, output.column_count, context.temp_allocator)
- output.table = make([][]string, output.row_count, context.temp_allocator)
- for &row in output.table {
- row = make([]string, output.column_count, context.temp_allocator)
- }
-
- case .SD8:
- count_stage : Stages = .INFO
- for line, l in lines {
- #partial switch count_stage {
- case .INFO:
- if line == "," {
- count_stage = .HEADER
- continue
- } else if len(line) > 2 {
- output.info_line_count += 1
- }
- case .HEADER:
- if len(line) > 2 {
- // Missing comma at the en in 8-series report, v therefore + 1
- output.column_count = strings.count(line, ",") + 1
- count_stage = .BODY
- }
- case .BODY:
- if len(line)>2 {
- output.row_count += 1
- }
- }
- }
- output.info_lines = make([]Info_Line, output.info_line_count+output.column_count, context.temp_allocator)
- output.header = make([]string, output.column_count, context.temp_allocator)
- output.table = make([][]string, output.row_count, context.temp_allocator)
- for &row in output.table {
- row = make([]string, output.column_count, context.temp_allocator)
- }
-
- case .UNSET:
- unreachable()
- }
-
-
- // STAGE 3 --------------------------------------------------------------
- // Filling with data
-
- if VERBOSE do fmt.printf("Struct before main parse:\n%#v\n", output)
-
- first_channel_index := -1
- last_channel_index := -1
-
- stage : Stages = .TITLE
- #partial switch device {
- case .SD8:
- // .d8888b. 8888888b. .d8888b.
- // d88P Y88b 888 "Y88b d88P Y88b
- // Y88b. 888 888 Y88b. d88P
- // "Y888b. 888 888 "Y88888"
- // "Y88b. 888 888 .d8P""Y8b.
- // "888 888 888 888 888
- // Y88b d88P 888 .d88P Y88b d88P
- // "Y8888P" 8888888P" "Y8888P"
- fmt.printf("Parsing [{}] as Sound Devices 8XX report, ", file_info.name)
- if strings.contains(file_info.name, "_1.CSV") || strings.contains(file_info.name, "_2.CSV") {
- output.title = file_info.name[7:len(file_info.name)-6]
- } else {
- output.title = file_info.name
- }
- fmt.printf("titled \"{}\".\n", output.title)
-
- info_line_index := 0
- body_line_index := 0
- for line, line_index in lines {
- switch stage {
- case .TITLE:
- // Missing newline on 8-series means we get info on the title line
- stage = .INFO
-
- line_elements := strings.split(line, ",")
- if VERBOSE do fmt.printf(".INFO {}: {}\n", line_index, line_elements)
- field := fmt.aprintf("{}:", line_elements[1], allocator=context.temp_allocator)
- entry := line_elements[2]
- output.info_lines[info_line_index].field = field
- output.info_lines[info_line_index].entry = entry
- info_line_index += 1
-
-
- case .INFO:
- if line == "," {
- stage = .HEADER
- continue
- }
- line_elements := strings.split(line, ",")
- if VERBOSE do fmt.printf(".INFO {}: {}\n", line_index, line_elements)
- if line_elements[0] == "Date" {
- if VERBOSE do fmt.printf("Skipping line {}, because it's the retarded date field on an 8-series\n", line_index)
- output.info_line_count -= 1
- continue
- }
- field := fmt.aprintf("{}:", line_elements[0], allocator=context.temp_allocator)
- entry := line_elements[1]
- output.info_lines[info_line_index].field = field
- output.info_lines[info_line_index].entry = entry
- info_line_index += 1
-
-
- case .HEADER:
- if line == "," {
- continue // This is here because there are a bunch of lines that are just commas before the header
- } else if len(line)>3 {
- if VERBOSE do fmt.printf(".HEADER {}:", line_index)
- // No trailing comma in the header??
- for element, e in strings.split(line, ",") {
- if VERBOSE do fmt.printf(" {}", element)
-
- output.header[e] = element
- if element[:3] == "Trk" {
- if first_channel_index == -1 do first_channel_index = e
- last_channel_index = e
- output.header[e] = fmt.aprintf("Trk {}", e-first_channel_index+1, allocator=context.temp_allocator)
- }
- if element == "Start TC" {
- output.tc_column_index = e
- }
- }
-
- if VERBOSE do fmt.printf("\n")
- if VERBOSE do fmt.printf("first_channel_index: {}\n", first_channel_index)
- if VERBOSE do fmt.printf("last_channel_index: {}\n", last_channel_index)
- stage = .BODY
- }
-
-
- case .BODY:
- if len(line) > 2 {
- if VERBOSE do fmt.printf(".BODY {}:", line_index)
- for element, e in strings.split(line, ",") {
- if VERBOSE do fmt.printf(" {}", element)
- entry : string = element
- output.table[body_line_index][e] = entry
- }
- if VERBOSE do fmt.printf("\n")
- body_line_index += 1
- }
-
-
- }
- }
-
- fmt.printf("\n")
-
-
-
-
- case .SD6:
- // .d8888b. 8888888b. .d8888b.
- // d88P Y88b 888 "Y88b d88P Y88b
- // Y88b. 888 888 888
- // "Y888b. 888 888 888d888b.
- // "Y88b. 888 888 888P "Y88b
- // "888 888 888 888 888
- // Y88b d88P 888 .d88P Y88b d88P
- // "Y8888P" 8888888P" "Y8888P"
- fmt.printf("Parsing [{}] as Sound Devices 6XX report, ", file_info.name)
- if file_info.name[len(file_info.name)-11:len(file_info.name)-3] == "_Report." {
- output.title = file_info.name[:len(file_info.name)-11]
- } else {
- output.title = file_info.name
- }
- fmt.printf("titled \"{}\".\n", output.title)
-
- info_line_index := 0
- body_line_index := 0
- for line, line_index in lines {
- switch stage {
- case .TITLE:
- if line_index == 1 { // Ugly magic number, could be fucked by firmware update
- stage = .INFO
- }
-
- case .INFO:
- if line == "," {
- stage = .HEADER
- continue
- }
- line_elements := strings.split(line, ",")
- if VERBOSE do fmt.printf(".INFO {}: {}\n", line_index, line_elements)
- field := line_elements[0]
- entry_raw := line_elements[1]
- entry := line_elements[1][1:len(entry_raw)-1]
- output.info_lines[info_line_index].field = field
- output.info_lines[info_line_index].entry = entry
- info_line_index += 1
-
-
- case .HEADER:
- if line == "," {
- // This is here because there are a bunch of lines that are just commas before the header
- } else if len(line)>3 {
- if VERBOSE do fmt.printf(".HEADER {}:", line_index)
- // No trailing comma in the header??
- for element, e in strings.split(line, ",") {
- if VERBOSE do fmt.printf(" {}", element)
-
- output.header[e] = element
- if element[:4] == "Trk " {
- if first_channel_index == -1 do first_channel_index = e
- last_channel_index = e
- // TODO: Rename track column with corrected numbers
- }
- if element == "Start TC" {
- output.tc_column_index = e
- }
- }
-
- if VERBOSE do fmt.printf("\n")
- } else if line == "" {
- stage = .BODY
- if VERBOSE do fmt.printf("first_channel_index: {}\n", first_channel_index)
- if VERBOSE do fmt.printf("last_channel_index: {}\n", last_channel_index)
- }
-
-
- case .BODY:
- if len(line) > 2 {
- if VERBOSE do fmt.printf(".BODY {}:", line_index)
- // to skip empty entry after trailing comma we do a silly slice
- for element, e in strings.split(line, ",")[:output.column_count] {
- if VERBOSE do fmt.printf(" {}", element)
- entry : string = element
- // Stripping quotes if after tracks begin
- if e >= first_channel_index && (len(element)>0) {
- entry = element[1:len(element)-1]
- }
- output.table[body_line_index][e] = entry
- }
- if VERBOSE do fmt.printf("\n")
- body_line_index += 1
- }
-
-
- }
- }
-
-
- case .ZOOM:
- // 8888888888 .d8888b.
- // 888 d88P Y88b
- // 888 Y88b. d88P
- // 8888888 "Y88888"
- // 888 .d8P""Y8b.
- // 888 888 888
- // 888 Y88b d88P
- // 888 "Y8888P"
- fmt.printf("Parsing [{}] as ZOOM report, ", file_info.name)
-
- // Getting title
- if file_info.name[:8] == "F8n Pro_" {
- output.title = file_info.name[8:len(file_info.name)-4]
- } else if file_info.name[:4] == "F8n_" { // I don't own one, so I don't know if this is what the F8n does
- output.title = file_info.name[4:len(file_info.name)-4]
- } else if file_info.name[:3] == "F8_" { // TODO: Verify this is what the original F8 does
- output.title = file_info.name[4:len(file_info.name)-4]
- } else {
- output.title = file_info.name
- }
- fmt.printf("titled \"{}\".\n", output.title)
-
- info_line_index := 0
- body_line_index := 0
- for line, line_index in lines {
- switch stage {
- case .TITLE:
- if line_index == 1 { // Ugly magic number, could be fucked by firmware update
- stage = .INFO
- }
-
- case .INFO:
- if line == "" {
- stage = .HEADER
- continue
- }
- line_elements := strings.split(line, ",")
- if VERBOSE do fmt.printf(".INFO {}: {}\n", line_index, line_elements)
- field_raw := line_elements[0]
- entry_raw := line_elements[1]
- field := line_elements[0][1:len(field_raw)-1]
- entry := line_elements[1][1:len(entry_raw)-1]
- output.info_lines[info_line_index].field = field
- output.info_lines[info_line_index].entry = entry
- info_line_index += 1
-
- case .HEADER:
- if VERBOSE do fmt.printf(".HEADER {}:", line_index)
- // to skip empty entry after trailing comma we do a silly slice
- for element, e in strings.split(line, ",")[:output.column_count] {
- if VERBOSE do fmt.printf(" {}", element)
- output.header[e] = element[1:len(element)-1]
-
- if element[:4] == "\"Tr " {
- if first_channel_index==-1 do first_channel_index = e
- last_channel_index = e
- output.header[e] = fmt.aprintf("Trk {}", e-first_channel_index+1, allocator=context.temp_allocator)
- }
- if element == "\"Start TC\"" {
- output.tc_column_index = e
- }
- }
- if VERBOSE do fmt.printf("\n")
- stage = .BODY
- if VERBOSE do fmt.printf("first_channel_index: {}\n", first_channel_index)
- if VERBOSE do fmt.printf("last_channel_index: {}\n", last_channel_index)
-
- case .BODY:
- if line == "" do break
- if VERBOSE do fmt.printf(".BODY {}:", line_index)
- // to skip empty entry after trailing comma we do a silly slice
- for element, e in strings.split(line, ",")[:output.column_count] {
- if VERBOSE do fmt.printf(" {}", element)
- output.table[body_line_index][e] = element[1:len(element)-1]
- }
- if VERBOSE do fmt.printf("\n")
- body_line_index += 1
- }
- }
- }
-
-
-
-
- // STAGE 4 --------------------------------------------------------------
- // Cleanup!
- if VERBOSE do fmt.printf("Struct before cleanup:\n%#v\n", output)
-
- // Stacking tracks to the left
- for &line, l in output.table {
- stacking_index := first_channel_index
- for &field, f in line[first_channel_index:last_channel_index+1] {
- if field != "" {
- line[stacking_index] = field
- stacking_index += 1
- }
- }
- for &field, f in line[stacking_index:last_channel_index+1] {
- field = ""
- }
- }
-
-
- // Cleaning out unused columns
- touched := make([]bool, output.column_count, context.temp_allocator)
- // Finding them
- for line, l in output.table {
- for field, f in line {
- if touched[f] do continue
- if field != "" {
- touched[f] = true
- }
- }
- }
-
- // Turning unchanging columns into info line
- changed := make([]bool, output.column_count, context.temp_allocator)
- prev_line : []string = nil
- for line, l in output.table {
- if l>0 {
- prev_line = output.table[l - 1]
- for field, f in line {
- if (prev_line[f] != field) ||
- (first_channel_index <= f && f <= last_channel_index) ||
- (f == output.tc_column_index) {
- changed[f] = true
- }
- }
- }
- }
- for did_change, i in changed {
- if (!did_change) && touched[i] {
- field := fmt.aprintf("{}: ", output.header[i], allocator=context.temp_allocator)
- entry := prev_line[i]
- output.info_lines[output.info_line_count] = {field=field, entry=entry}
- output.info_line_count += 1
- }
- }
-
-
- // Removing unused and static
- for &line, l in output.table {
- stacking_index := 0
- for &field, f in line {
- if touched[f] && changed[f] {
- line[stacking_index] = field
- stacking_index += 1
- }
- }
- for &field, f in line[stacking_index:] {
- field = ""
- }
- }
- stacking_index := 0
- for &field, f in output.header {
- if touched[f] && changed[f] {
- output.header[stacking_index] = field
- stacking_index += 1
- }
- }
- for &field, f in output.header[stacking_index:] {
- field = ""
- }
-
- output.column_count = stacking_index
-
- if VERBOSE do fmt.printf("Struct before output:\n%#v\n", output)
-
- return output, true
-}
-
-
-render :: proc(report : Report, path : string) {
- // Now we output the HTML.
-
- builder := strings.builder_make(context.temp_allocator)
-
- strings.write_string(&builder, PART_ONE)
- strings.write_string(&builder, report.title)
- strings.write_string(&builder, " - Lydrapport")
- strings.write_string(&builder, PART_TWO)
-
-
- for line, l in report.info_lines[:report.info_line_count] {
- strings.write_string(&builder, " <p><b>")
- strings.write_string(&builder, line.field)
- strings.write_string(&builder, "</b> ")
- strings.write_string(&builder, line.entry)
- strings.write_string(&builder, "</p>\n")
- }
- strings.write_string(&builder, " </div>\n </div>\n <table>\n <thead>\n <tr class=\"header-tr\">\n")
-
- for field, f in report.header[:report.column_count] {
- if f != report.tc_column_index {
- strings.write_string(&builder, " <th>")
- } else {
- strings.write_string(&builder, " <th class=\"current-sort\">")
- }
- strings.write_string(&builder, field)
- strings.write_string(&builder, "</th>\n")
- }
-
- strings.write_string(&builder, " </tr>\n </thead>\n <tbody>\n")
-
- for line, l in report.table {
- strings.write_string(&builder, " <tr>\n")
- for field, f in line[:report.column_count] {
- strings.write_string(&builder, " <td>")
- strings.write_string(&builder, field)
- strings.write_string(&builder, "</td>\n")
-
- }
- strings.write_string(&builder, " </tr>\n")
- }
-
- strings.write_string(&builder, PART_END)
-
- output_text := strings.to_string(builder)
- os.write_entire_file(path, transmute([]u8)output_text)
-
- fmt.printf("Output: {}\n", path)
-}
-
-
-
-
-indent_by :: proc(i : int) {
- for x in 0..<i {
- fmt.printf(" ")
- }
-}
-
-walk_directory :: proc(path : string, file_number : ^int, file_list : ^[dynamic]string, depth : int = 0) -> bool {
- handle, ok := os.open(path)
- if ok != os.ERROR_NONE {
- indent_by(depth)
- fmt.printf("ERROR opening dir: %s\n", path)
- return false
- }
- defer os.close(handle)
-
- files, okr := os.read_dir(handle, -1, context.temp_allocator)
- if okr != os.ERROR_NONE {
- indent_by(depth)
- fmt.printf("ERROR [{}] reading dir: %s\n", okr, path)
- if okr == os.ERROR_FILE_IS_NOT_DIR do return true
- return true
- }
-
-
- for file in files {
-
- full_path := file.fullpath
-
- if file.is_dir {
- indent_by(depth)
- fmt.printf("šŸ“ %s\n", file.name)
- walk_directory(full_path, file_number, file_list, depth+1) // Recurse
-
- } else { // If file is actually a file
-
- extension := strings.to_lower(filepath.ext(file.name))
- defer delete(extension)
- if(extension == ".csv"){
- indent_by(depth)
- fmt.printf("šŸ“„ %d %s\n", file_number^, file.name)
- append(file_list, strings.clone(file.fullpath))
- file_number^ += 1
- }
- }
- }
- return false
-}
-
-walk_directory_os2 :: proc(path : string, file_number : ^int, file_list : ^[dynamic]string, depth : int = 0) {
- handle, ok := os2.open(path)
- if ok != os2.ERROR_NONE {
- indent_by(depth)
- fmt.printf("ERROR opening dir: %s\n", path)
- return
- }
- defer os2.close(handle)
-
- files, okr := os2.read_dir(handle, -1, context.temp_allocator)
- if okr != os2.ERROR_NONE {
- indent_by(depth)
- fmt.printf("ERROR [{}] reading dir: %s\n", okr, path)
- return
- }
-
-
- for file in files {
-
- full_path := file.fullpath
-
- if os.is_dir(full_path) {
- indent_by(depth)
- fmt.printf("šŸ“ %s\n", file.name)
- walk_directory_os2(full_path, file_number, file_list, depth+1) // Recurse
-
- } else { // If file is actually a file
-
- extension := strings.to_lower(filepath.ext(file.name))
- defer delete(extension)
- if(extension == ".csv"){
- indent_by(depth)
- fmt.printf("šŸ“„ %d %s\n", file_number^, file.name)
- append(file_list, strings.clone(file.fullpath))
- file_number^ += 1
- }
- }
- }
+package main
+
+import "core:fmt"
+import "core:os"
+import "core:os/os2"
+import "core:path/filepath"
+import "core:sys/windows"
+import "core:strings"
+
+/*
+TODO: Simplify pre-allocation. Just allocate a bunch. It's probably fine.
+ Maybe do it by just counting how many lines are longer than 2 characters.
+TODO: Drag-n-drop window if no files are specified
+*/
+
+VERBOSE :: false
+INCLUDE_DATE :: false // By default I delete the retarded date field that says what day the report was generated.
+
+PART_ONE :: #load("parts/start.html", string)
+PART_TWO :: #load("parts/start2.html", string)
+PART_END :: #load("parts/end.html", string)
+
+Device :: enum {
+ UNSET,
+ ZOOM,
+ SD6,
+ SD8, // Tested with 888
+}
+
+Stages :: enum {
+ TITLE,
+ INFO,
+ HEADER,
+ BODY,
+}
+
+Info_Line :: struct {
+ field : string,
+ entry : string,
+}
+
+Report :: struct {
+ title : string,
+ info_lines : []Info_Line,
+ header : []string,
+ table : [][]string,
+ column_count : int,
+ row_count : int,
+ info_line_count : int,
+ tc_column_index : int,
+}
+
+main :: proc() {
+ when ODIN_OS == .Windows {
+ windows.SetConsoleOutputCP(windows.CODEPAGE.UTF8)
+ }
+
+ input_file_name : string
+ if len(os.args) < 2 {
+ fmt.println("ERROR: No file submitted.")
+ return
+ } else {
+ fmt.printf("Input path: {}\n", os.args[1])
+ input_file_name = os.args[1]
+ }
+
+
+ path_info, error := os.stat(input_file_name)
+
+ file_count := 1
+ files_done := 0
+ if error == os.ERROR_NONE {
+
+ file_list : [dynamic]string
+ if(path_info.is_dir) {
+ fmt.printf("Directory submitted! Walking directory...\n\n")
+ fmt.printf("šŸ“ {}\n", path_info.name)
+ try_os2 := walk_directory(path_info.fullpath, &file_count, &file_list, 1)
+ if len(file_list) == 0 && try_os2 {
+ fmt.printf("\nNot_Dir error encountered. Trying os2 version...\n\n")
+ fmt.printf("šŸ“ {}\n", path_info.name)
+ walk_directory_os2(path_info.fullpath, &file_count, &file_list, 1)
+ }
+ } else {
+ fmt.println("File submitted! Processing file...")
+ append(&file_list, strings.clone(path_info.fullpath))
+ }
+
+ for file, f in file_list {
+
+ file_info, _ := os.stat(file)
+ fmt.printf("\nšŸ“„ File {}: {}\n", f+1, file_info.name)
+ parsed, ok_parse := parse(file_info.fullpath)
+ if !ok_parse {
+ fmt.printf("Parse failed: {}\n", file_info.fullpath)
+ continue
+ }
+ output_name := fmt.aprintf("{}/{}_Knekt_Lydrapport.html", filepath.dir(file_info.fullpath), parsed.title, allocator=context.temp_allocator)
+ render(parsed, output_name)
+ free_all(context.temp_allocator)
+ files_done += 1
+ }
+ fmt.printf("\nCompleted {}/{} files.\n\n", files_done, len(file_list))
+ } else {
+ fmt.printf("ERROR could not get path info for: {}\n", input_file_name)
+ }
+
+}
+
+
+
+
+parse :: proc(path : string, device : Device = .UNSET) -> (Report, bool) {
+ device := device
+ output : Report = {}
+ data, ok := os.read_entire_file(path, context.temp_allocator)
+ if !ok {
+ fmt.printf("ERROR: Could not read file: {}\n", path)
+ return {}, false
+ }
+ file_info, _ := os.lstat(path, context.temp_allocator)
+
+
+ lines := strings.split_lines(string(data), allocator=context.temp_allocator)
+
+
+ // STAGE 1 --------------------------------------------------------------
+ // First, we detect what kind of sound report this is
+ for line, line_number in lines[:3] {
+ if (device!=.UNSET) { break }
+ if line == "\"SOUND REPORT\"," {
+ device = .ZOOM
+ if VERBOSE do fmt.printf("Detected ZOOM from quotes and comma on line index {}\n", line_number)
+ }
+ if line == "\"ZOOM F8\"," {
+ device = .ZOOM
+ if VERBOSE do fmt.printf("Detected ZOOM from \"ZOOM F8\" on line index {}\n", line_number)
+ }
+ if line == "SOUND REPORT" {
+ device = .SD6
+ if VERBOSE do fmt.printf("Detected SOUND_DEVICES from unquoted SOUND REPORT line index {}\n", line_number)
+ }
+ if len(line)<15 do continue
+ if line[:13] == "SOUND REPORT," {
+ device = .SD8
+ if VERBOSE do fmt.printf("Detected SOUND_DEVICES 8-series from SOUND REPORT with missing newline on line index {}\n", line_number)
+ }
+ }
+
+ if device == .UNSET {
+ fmt.printf("ERROR: Unable to detect sound report type!\n")
+ return {}, false
+ }
+
+
+
+ // STAGE 2 --------------------------------------------------------------
+ // Measuring content for allocation
+
+ switch device {
+ case .ZOOM:
+ output.column_count = 21 // Ugly magic number, could be fucked by firmware update
+
+ // Padded for expanding info lines from unchanging columns
+ output.info_lines = make([]Info_Line, 2+output.column_count, context.temp_allocator)
+ output.info_line_count = 2
+
+ output.row_count = strings.count(string(data), "\n") - 7 // Ugly magic number, could be fucked by firmware update
+ output.table = make([][]string, output.row_count, context.temp_allocator)
+ output.header = make([]string, output.column_count, context.temp_allocator)
+ for &row in output.table {
+ row = make([]string, output.column_count, context.temp_allocator)
+ }
+ case .SD6:
+ second_to_last_line := lines[len(lines)-2]
+ output.column_count = strings.count(second_to_last_line, ",")
+ count_stage : Stages = .TITLE
+ for line, l in lines {
+ switch count_stage {
+ case .TITLE:
+ if l == 1 { // Ugly magic number, could be fucked by firmware update
+ count_stage = .INFO
+ }
+ case .INFO:
+ if line == "," {
+ count_stage = .HEADER
+ continue
+ } else if len(line) > 2 {
+ output.info_line_count += 1
+ }
+ case .HEADER:
+ if line == "" {
+ count_stage = .BODY
+ }
+ case .BODY:
+ if len(line)>2 {
+ output.row_count += 1
+ }
+ }
+ }
+ output.info_lines = make([]Info_Line, output.info_line_count+output.column_count, context.temp_allocator)
+ output.header = make([]string, output.column_count, context.temp_allocator)
+ output.table = make([][]string, output.row_count, context.temp_allocator)
+ for &row in output.table {
+ row = make([]string, output.column_count, context.temp_allocator)
+ }
+
+ case .SD8:
+ count_stage : Stages = .INFO
+ for line, l in lines {
+ #partial switch count_stage {
+ case .INFO:
+ if line == "," {
+ count_stage = .HEADER
+ continue
+ } else if len(line) > 2 {
+ output.info_line_count += 1
+ }
+ case .HEADER:
+ if len(line) > 2 {
+ // Missing comma at the en in 8-series report, v therefore + 1
+ output.column_count = strings.count(line, ",") + 1
+ count_stage = .BODY
+ }
+ case .BODY:
+ if len(line)>2 {
+ output.row_count += 1
+ }
+ }
+ }
+ output.info_lines = make([]Info_Line, output.info_line_count+output.column_count, context.temp_allocator)
+ output.header = make([]string, output.column_count, context.temp_allocator)
+ output.table = make([][]string, output.row_count, context.temp_allocator)
+ for &row in output.table {
+ row = make([]string, output.column_count, context.temp_allocator)
+ }
+
+ case .UNSET:
+ unreachable()
+ }
+
+
+ // STAGE 3 --------------------------------------------------------------
+ // Filling with data
+
+ if VERBOSE do fmt.printf("Struct before main parse:\n%#v\n", output)
+
+ first_channel_index := -1
+ last_channel_index := -1
+
+ stage : Stages = .TITLE
+ #partial switch device {
+ case .SD8:
+ // .d8888b. 8888888b. .d8888b.
+ // d88P Y88b 888 "Y88b d88P Y88b
+ // Y88b. 888 888 Y88b. d88P
+ // "Y888b. 888 888 "Y88888"
+ // "Y88b. 888 888 .d8P""Y8b.
+ // "888 888 888 888 888
+ // Y88b d88P 888 .d88P Y88b d88P
+ // "Y8888P" 8888888P" "Y8888P"
+ fmt.printf("Parsing [{}] as Sound Devices 8XX report, ", file_info.name)
+ if strings.contains(file_info.name, "_1.CSV") || strings.contains(file_info.name, "_2.CSV") {
+ output.title = file_info.name[7:len(file_info.name)-6]
+ } else {
+ output.title = file_info.name
+ }
+ fmt.printf("titled \"{}\".\n", output.title)
+
+ info_line_index := 0
+ body_line_index := 0
+ for line, line_index in lines {
+ switch stage {
+ case .TITLE:
+ // Missing newline on 8-series means we get info on the title line
+ stage = .INFO
+
+ line_elements := strings.split(line, ",")
+ if VERBOSE do fmt.printf(".INFO {}: {}\n", line_index, line_elements)
+ field := fmt.aprintf("{}:", line_elements[1], allocator=context.temp_allocator)
+ entry := line_elements[2]
+ output.info_lines[info_line_index].field = field
+ output.info_lines[info_line_index].entry = entry
+ info_line_index += 1
+
+
+ case .INFO:
+ if line == "," {
+ stage = .HEADER
+ continue
+ }
+ line_elements := strings.split(line, ",")
+ if VERBOSE do fmt.printf(".INFO {}: {}\n", line_index, line_elements)
+ if line_elements[0] == "Date" {
+ if VERBOSE do fmt.printf("Skipping line {}, because it's the retarded date field on an 8-series\n", line_index)
+ output.info_line_count -= 1
+ continue
+ }
+ field := fmt.aprintf("{}:", line_elements[0], allocator=context.temp_allocator)
+ entry := line_elements[1]
+ output.info_lines[info_line_index].field = field
+ output.info_lines[info_line_index].entry = entry
+ info_line_index += 1
+
+
+ case .HEADER:
+ if line == "," {
+ continue // This is here because there are a bunch of lines that are just commas before the header
+ } else if len(line)>3 {
+ if VERBOSE do fmt.printf(".HEADER {}:", line_index)
+ // No trailing comma in the header??
+ for element, e in strings.split(line, ",") {
+ if VERBOSE do fmt.printf(" {}", element)
+
+ output.header[e] = element
+ if element[:3] == "Trk" {
+ if first_channel_index == -1 do first_channel_index = e
+ last_channel_index = e
+ output.header[e] = fmt.aprintf("Trk {}", e-first_channel_index+1, allocator=context.temp_allocator)
+ }
+ if element == "Start TC" {
+ output.tc_column_index = e
+ }
+ }
+
+ if VERBOSE do fmt.printf("\n")
+ if VERBOSE do fmt.printf("first_channel_index: {}\n", first_channel_index)
+ if VERBOSE do fmt.printf("last_channel_index: {}\n", last_channel_index)
+ stage = .BODY
+ }
+
+
+ case .BODY:
+ if len(line) > 2 {
+ if VERBOSE do fmt.printf(".BODY {}:", line_index)
+ for element, e in strings.split(line, ",") {
+ if VERBOSE do fmt.printf(" {}", element)
+ entry : string = element
+ output.table[body_line_index][e] = entry
+ }
+ if VERBOSE do fmt.printf("\n")
+ body_line_index += 1
+ }
+
+
+ }
+ }
+
+ fmt.printf("\n")
+
+
+
+
+ case .SD6:
+ // .d8888b. 8888888b. .d8888b.
+ // d88P Y88b 888 "Y88b d88P Y88b
+ // Y88b. 888 888 888
+ // "Y888b. 888 888 888d888b.
+ // "Y88b. 888 888 888P "Y88b
+ // "888 888 888 888 888
+ // Y88b d88P 888 .d88P Y88b d88P
+ // "Y8888P" 8888888P" "Y8888P"
+ fmt.printf("Parsing [{}] as Sound Devices 6XX report, ", file_info.name)
+ if file_info.name[len(file_info.name)-11:len(file_info.name)-3] == "_Report." {
+ output.title = file_info.name[:len(file_info.name)-11]
+ } else {
+ output.title = file_info.name
+ }
+ fmt.printf("titled \"{}\".\n", output.title)
+
+ info_line_index := 0
+ body_line_index := 0
+ for line, line_index in lines {
+ switch stage {
+ case .TITLE:
+ if line_index == 1 { // Ugly magic number, could be fucked by firmware update
+ stage = .INFO
+ }
+
+ case .INFO:
+ if line == "," {
+ stage = .HEADER
+ continue
+ }
+ line_elements := strings.split(line, ",")
+ if VERBOSE do fmt.printf(".INFO {}: {}\n", line_index, line_elements)
+ field := line_elements[0]
+ entry_raw := line_elements[1]
+ entry := line_elements[1][1:len(entry_raw)-1]
+ output.info_lines[info_line_index].field = field
+ output.info_lines[info_line_index].entry = entry
+ info_line_index += 1
+
+
+ case .HEADER:
+ if line == "," {
+ // This is here because there are a bunch of lines that are just commas before the header
+ } else if len(line)>3 {
+ if VERBOSE do fmt.printf(".HEADER {}:", line_index)
+ // No trailing comma in the header??
+ for element, e in strings.split(line, ",") {
+ if VERBOSE do fmt.printf(" {}", element)
+
+ output.header[e] = element
+ if element[:4] == "Trk " {
+ if first_channel_index == -1 do first_channel_index = e
+ last_channel_index = e
+ // TODO: Rename track column with corrected numbers
+ }
+ if element == "Start TC" {
+ output.tc_column_index = e
+ }
+ }
+
+ if VERBOSE do fmt.printf("\n")
+ } else if line == "" {
+ stage = .BODY
+ if VERBOSE do fmt.printf("first_channel_index: {}\n", first_channel_index)
+ if VERBOSE do fmt.printf("last_channel_index: {}\n", last_channel_index)
+ }
+
+
+ case .BODY:
+ if len(line) > 2 {
+ if VERBOSE do fmt.printf(".BODY {}:", line_index)
+ // to skip empty entry after trailing comma we do a silly slice
+ for element, e in strings.split(line, ",")[:output.column_count] {
+ if VERBOSE do fmt.printf(" {}", element)
+ entry : string = element
+ // Stripping quotes if after tracks begin
+ if e >= first_channel_index && (len(element)>0) {
+ entry = element[1:len(element)-1]
+ }
+ output.table[body_line_index][e] = entry
+ }
+ if VERBOSE do fmt.printf("\n")
+ body_line_index += 1
+ }
+
+
+ }
+ }
+
+
+ case .ZOOM:
+ // 8888888888 .d8888b.
+ // 888 d88P Y88b
+ // 888 Y88b. d88P
+ // 8888888 "Y88888"
+ // 888 .d8P""Y8b.
+ // 888 888 888
+ // 888 Y88b d88P
+ // 888 "Y8888P"
+ fmt.printf("Parsing [{}] as ZOOM report, ", file_info.name)
+
+ // Getting title
+ if file_info.name[:8] == "F8n Pro_" {
+ output.title = file_info.name[8:len(file_info.name)-4]
+ } else if file_info.name[:4] == "F8n_" { // I don't own one, so I don't know if this is what the F8n does
+ output.title = file_info.name[4:len(file_info.name)-4]
+ } else if file_info.name[:3] == "F8_" { // TODO: Verify this is what the original F8 does
+ output.title = file_info.name[4:len(file_info.name)-4]
+ } else {
+ output.title = file_info.name
+ }
+ fmt.printf("titled \"{}\".\n", output.title)
+
+ info_line_index := 0
+ body_line_index := 0
+ for line, line_index in lines {
+ switch stage {
+ case .TITLE:
+ if line_index == 1 { // Ugly magic number, could be fucked by firmware update
+ stage = .INFO
+ }
+
+ case .INFO:
+ if line == "" {
+ stage = .HEADER
+ continue
+ }
+ line_elements := strings.split(line, ",")
+ if VERBOSE do fmt.printf(".INFO {}: {}\n", line_index, line_elements)
+ field_raw := line_elements[0]
+ entry_raw := line_elements[1]
+ field := line_elements[0][1:len(field_raw)-1]
+ entry := line_elements[1][1:len(entry_raw)-1]
+ output.info_lines[info_line_index].field = field
+ output.info_lines[info_line_index].entry = entry
+ info_line_index += 1
+
+ case .HEADER:
+ if VERBOSE do fmt.printf(".HEADER {}:", line_index)
+ // to skip empty entry after trailing comma we do a silly slice
+ for element, e in strings.split(line, ",")[:output.column_count] {
+ if VERBOSE do fmt.printf(" {}", element)
+ output.header[e] = element[1:len(element)-1]
+
+ if element[:4] == "\"Tr " {
+ if first_channel_index==-1 do first_channel_index = e
+ last_channel_index = e
+ output.header[e] = fmt.aprintf("Trk {}", e-first_channel_index+1, allocator=context.temp_allocator)
+ }
+ if element == "\"Start TC\"" {
+ output.tc_column_index = e
+ }
+ }
+ if VERBOSE do fmt.printf("\n")
+ stage = .BODY
+ if VERBOSE do fmt.printf("first_channel_index: {}\n", first_channel_index)
+ if VERBOSE do fmt.printf("last_channel_index: {}\n", last_channel_index)
+
+ case .BODY:
+ if line == "" do break
+ if VERBOSE do fmt.printf(".BODY {}:", line_index)
+ // to skip empty entry after trailing comma we do a silly slice
+ for element, e in strings.split(line, ",")[:output.column_count] {
+ if VERBOSE do fmt.printf(" {}", element)
+ output.table[body_line_index][e] = element[1:len(element)-1]
+ }
+ if VERBOSE do fmt.printf("\n")
+ body_line_index += 1
+ }
+ }
+ }
+
+
+
+
+ // STAGE 4 --------------------------------------------------------------
+ // Cleanup!
+ if VERBOSE do fmt.printf("Struct before cleanup:\n%#v\n", output)
+
+ // Stacking tracks to the left
+ for &line, l in output.table {
+ stacking_index := first_channel_index
+ for &field, f in line[first_channel_index:last_channel_index+1] {
+ if field != "" {
+ line[stacking_index] = field
+ stacking_index += 1
+ }
+ }
+ for &field, f in line[stacking_index:last_channel_index+1] {
+ field = ""
+ }
+ }
+
+
+ // Cleaning out unused columns
+ touched := make([]bool, output.column_count, context.temp_allocator)
+ // Finding them
+ for line, l in output.table {
+ for field, f in line {
+ if touched[f] do continue
+ if field != "" {
+ touched[f] = true
+ }
+ }
+ }
+
+ // Turning unchanging columns into info line
+ changed := make([]bool, output.column_count, context.temp_allocator)
+ prev_line : []string = nil
+ for line, l in output.table {
+ if l>0 {
+ prev_line = output.table[l - 1]
+ for field, f in line {
+ if (prev_line[f] != field) ||
+ (first_channel_index <= f && f <= last_channel_index) ||
+ (f == output.tc_column_index) {
+ changed[f] = true
+ }
+ }
+ }
+ }
+ for did_change, i in changed {
+ if (!did_change) && touched[i] {
+ field := fmt.aprintf("{}: ", output.header[i], allocator=context.temp_allocator)
+ entry := prev_line[i]
+ output.info_lines[output.info_line_count] = {field=field, entry=entry}
+ output.info_line_count += 1
+ }
+ }
+
+
+ // Removing unused and static
+ for &line, l in output.table {
+ stacking_index := 0
+ for &field, f in line {
+ if touched[f] && changed[f] {
+ line[stacking_index] = field
+ stacking_index += 1
+ }
+ }
+ for &field, f in line[stacking_index:] {
+ field = ""
+ }
+ }
+ stacking_index := 0
+ for &field, f in output.header {
+ if touched[f] && changed[f] {
+ output.header[stacking_index] = field
+ stacking_index += 1
+ }
+ }
+ for &field, f in output.header[stacking_index:] {
+ field = ""
+ }
+
+ output.column_count = stacking_index
+
+ if VERBOSE do fmt.printf("Struct before output:\n%#v\n", output)
+
+ return output, true
+}
+
+
+render :: proc(report : Report, path : string) {
+ // Now we output the HTML.
+
+ builder := strings.builder_make(context.temp_allocator)
+
+ strings.write_string(&builder, PART_ONE)
+ strings.write_string(&builder, report.title)
+ strings.write_string(&builder, " - Lydrapport")
+ strings.write_string(&builder, PART_TWO)
+
+
+ for line, l in report.info_lines[:report.info_line_count] {
+ strings.write_string(&builder, " <p><b>")
+ strings.write_string(&builder, line.field)
+ strings.write_string(&builder, "</b> ")
+ strings.write_string(&builder, line.entry)
+ strings.write_string(&builder, "</p>\n")
+ }
+ strings.write_string(&builder, " </div>\n </div>\n <table>\n <thead>\n <tr class=\"header-tr\">\n")
+
+ for field, f in report.header[:report.column_count] {
+ if f != report.tc_column_index {
+ strings.write_string(&builder, " <th>")
+ } else {
+ strings.write_string(&builder, " <th class=\"current-sort\">")
+ }
+ strings.write_string(&builder, field)
+ strings.write_string(&builder, "</th>\n")
+ }
+
+ strings.write_string(&builder, " </tr>\n </thead>\n <tbody>\n")
+
+ for line, l in report.table {
+ strings.write_string(&builder, " <tr>\n")
+ for field, f in line[:report.column_count] {
+ strings.write_string(&builder, " <td>")
+ strings.write_string(&builder, field)
+ strings.write_string(&builder, "</td>\n")
+
+ }
+ strings.write_string(&builder, " </tr>\n")
+ }
+
+ strings.write_string(&builder, PART_END)
+
+ output_text := strings.to_string(builder)
+ os.write_entire_file(path, transmute([]u8)output_text)
+
+ fmt.printf("Output: {}\n", path)
+}
+
+
+
+
+indent_by :: proc(i : int) {
+ for x in 0..<i {
+ fmt.printf(" ")
+ }
+}
+
+walk_directory :: proc(path : string, file_number : ^int, file_list : ^[dynamic]string, depth : int = 0) -> bool {
+ handle, ok := os.open(path)
+ if ok != os.ERROR_NONE {
+ indent_by(depth)
+ fmt.printf("ERROR opening dir: %s\n", path)
+ return false
+ }
+ defer os.close(handle)
+
+ files, okr := os.read_dir(handle, -1, context.temp_allocator)
+ if okr != os.ERROR_NONE {
+ indent_by(depth)
+ fmt.printf("ERROR [{}] reading dir: %s\n", okr, path)
+ if okr == os.ERROR_FILE_IS_NOT_DIR do return true
+ return true
+ }
+
+
+ for file in files {
+
+ full_path := file.fullpath
+
+ if file.is_dir {
+ indent_by(depth)
+ fmt.printf("šŸ“ %s\n", file.name)
+ walk_directory(full_path, file_number, file_list, depth+1) // Recurse
+
+ } else { // If file is actually a file
+
+ extension := strings.to_lower(filepath.ext(file.name))
+ defer delete(extension)
+ if(extension == ".csv"){
+ indent_by(depth)
+ fmt.printf("šŸ“„ %d %s\n", file_number^, file.name)
+ append(file_list, strings.clone(file.fullpath))
+ file_number^ += 1
+ }
+ }
+ }
+ return false
+}
+
+walk_directory_os2 :: proc(path : string, file_number : ^int, file_list : ^[dynamic]string, depth : int = 0) {
+ handle, ok := os2.open(path)
+ if ok != os2.ERROR_NONE {
+ indent_by(depth)
+ fmt.printf("ERROR opening dir: %s\n", path)
+ return
+ }
+ defer os2.close(handle)
+
+ files, okr := os2.read_dir(handle, -1, context.temp_allocator)
+ if okr != os2.ERROR_NONE {
+ indent_by(depth)
+ fmt.printf("ERROR [{}] reading dir: %s\n", okr, path)
+ return
+ }
+
+
+ for file in files {
+
+ full_path := file.fullpath
+
+ if os.is_dir(full_path) {
+ indent_by(depth)
+ fmt.printf("šŸ“ %s\n", file.name)
+ walk_directory_os2(full_path, file_number, file_list, depth+1) // Recurse
+
+ } else { // If file is actually a file
+
+ extension := strings.to_lower(filepath.ext(file.name))
+ defer delete(extension)
+ if(extension == ".csv"){
+ indent_by(depth)
+ fmt.printf("šŸ“„ %d %s\n", file_number^, file.name)
+ append(file_list, strings.clone(file.fullpath))
+ file_number^ += 1
+ }
+ }
+ }
} \ No newline at end of file
diff --git a/src/wav/wav.odin b/src/wav/wav.odin
new file mode 100644
index 0000000..04cb0cf
--- /dev/null
+++ b/src/wav/wav.odin
@@ -0,0 +1,140 @@
+package wav
+
+import "core:fmt"
+import "core:math"
+import "core:strings"
+import "core:os"
+
+Wav :: struct {
+ path : string,
+ handle : os.Handle,
+ buf : []u8,
+ reported_size : u32,
+ bext : string,
+ ixml : string,
+ format : Audio_Format,
+ channels : int,
+ channel_names : []string,
+ sample_rate : int,
+ bit_depth : int,
+}
+Audio_Format :: enum {
+ PCM = 1,
+ FLOAT = 3,
+}
+
+BUFFER_SIZE :: 1<<15
+
+main :: proc() {
+ sweden, sweden_ok := read_wav("test/sweden.wav", context.temp_allocator)
+ fmt.printf("\n\nsweden = %#v\n\n", sweden)
+ enok, enok_ok := read_wav("test/ENOKS-BIRHTDAYT02.WAV", context.temp_allocator)
+ fmt.printf("\n\nenok = %#v\n\n", enok)
+}
+
+
+read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) {
+ file : Wav
+
+ file_ok : os.Error
+ file.handle, file_ok = os.open(path)
+ defer os.close(file.handle)
+ assert(file_ok == os.General_Error.None)
+
+ file.buf = new([BUFFER_SIZE]u8)[:]
+ defer delete(file.buf)
+
+ os.read(file.handle, file.buf)
+
+
+ head : int = 0
+
+ // RIFF header
+ fmt.println(string(file.buf[0:4]))
+ if string(file.buf[0:4]) != "RIFF" do return {}, false
+ head += 4
+
+ // Size
+ file.reported_size = read_little_endian_u32(file.buf[head:head+4])
+ fmt.println("Reported size:", file.reported_size)
+ head += 4
+
+ // Confirming again that this is a wave file
+ fmt.println(string(file.buf[head:head+4]))
+ if string(file.buf[head:head+4]) != "WAVE" do return {}, false
+ head += 4
+
+ fmt.println("\nChunks:\n")
+
+ // Looping through chunks
+ for _ in 0..<BUFFER_SIZE {
+ chunk_id := string(file.buf[head:head+4])
+ head += 4
+ chunk_size := int(read_little_endian_u32(file.buf[head:head+4]))
+ head += 4
+ fmt.println(chunk_id,"\n-------------------------------------")
+
+ if head+chunk_size < BUFFER_SIZE {
+ print_data : string
+ data_end := head+chunk_size
+ read_end := min(head+15000, data_end)
+ switch chunk_id {
+ case "iXML":
+ print_data = string(file.buf[head:read_end])
+ file.ixml = strings.clone(string(file.buf[head:data_end]), allocator=allocator)
+ case "bext":
+ print_data = string(file.buf[head:read_end])
+ file.bext = strings.clone(string(file.buf[head:data_end]), allocator=allocator)
+ case "fmt ":
+ file.format = Audio_Format(read_little_endian_u16(file.buf[head:]))
+ head += 2
+ file.channels = int(read_little_endian_u16(file.buf[head:]))
+ head += 2
+ file.sample_rate = int(read_little_endian_u32(file.buf[head:]))
+ head += 4
+
+ // Skipping byte rate and block align.
+ // These two legacy fields are only ever redundant or wrong,
+ // and are implied by the other fields.
+ head += 4 + 2
+
+ file.bit_depth = int(read_little_endian_u16(file.buf[head:]))
+ head += 2
+ }
+ fmt.println(print_data, "\n")
+ } else {
+ break
+ }
+
+
+ head += chunk_size
+ }
+
+ file.channel_names = make([]string, file.channels)
+
+ naming_channel := 0
+ for line in strings.split_lines(file.bext) {
+ if strings.starts_with(line, "sTRK") {
+ eq_index := strings.index(line, "=")
+ file.channel_names[naming_channel] = strings.clone(line[eq_index+1:], allocator=allocator)
+ naming_channel += 1
+ }
+ }
+
+ delete(file.buf)
+ file.buf = nil
+ return file, true
+}
+
+
+read_little_endian_u32 :: proc(x : []u8) -> u32 {
+ return u32(x[0]) |
+ u32(x[1]) << 8 |
+ u32(x[2]) << 16 |
+ u32(x[3]) << 24
+}
+
+read_little_endian_u16 :: proc(x : []u8) -> u16 {
+ return u16(x[0]) |
+ u16(x[1]) << 8
+} \ No newline at end of file