aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/header_template.txt14
-rw-r--r--src/main.odin109
-rw-r--r--src/wav/wav.odin250
3 files changed, 223 insertions, 150 deletions
diff --git a/src/header_template.txt b/src/header_template.txt
new file mode 100644
index 0000000..8d42a55
--- /dev/null
+++ b/src/header_template.txt
@@ -0,0 +1,14 @@
+# Header info fields
+#
+# In this file, you can write things you want to appear in the header of the sound report.
+# These fields will be inserted into sound reports generated directly from wav metadata.
+# This happens when you submit a folder with .WAV-files that doesn't contain a CSV file.
+#
+# Empty lines, and lines starting with # are ignored.
+# No need to add facts about the files, those are added automatically from metadata.
+
+Sound Mixer: Ola Nordmann
+Phone: +0 123 45 678
+Email: ola@nordmann.no
+Boom Operator: Sven Svensson
+Project: Project Name
diff --git a/src/main.odin b/src/main.odin
index 71ae6ed..37c17c1 100644
--- a/src/main.odin
+++ b/src/main.odin
@@ -6,6 +6,7 @@ import "core:os/os2"
import "core:path/filepath"
import "core:sys/windows"
import "core:strings"
+import "wav"
/*
TODO: Simplify pre-allocation. Just allocate a bunch. It's probably fine.
@@ -20,6 +21,10 @@ PART_ONE :: #load("parts/start.html", string)
PART_TWO :: #load("parts/start2.html", string)
PART_END :: #load("parts/end.html", string)
+HEADER_TEMPLATE :: #load("header_template.txt", string)
+HEADER_FIELDS_PATH :: "info.txt"
+header_fields_file : string
+
Device :: enum {
UNSET,
ZOOM,
@@ -50,6 +55,12 @@ Report :: struct {
tc_column_index : int,
}
+CSV :: string
+Directory :: [dynamic]string
+Job :: union {CSV, Directory}
+job_list :: [dynamic]Job
+// TODO: Changing file_list to job_list, so the Directory jobs can contain a list of all the relevant .wav files before being sent to parse_folder()
+
main :: proc() {
when ODIN_OS == .Windows {
windows.SetConsoleOutputCP(windows.CODEPAGE.UTF8)
@@ -57,13 +68,20 @@ main :: proc() {
input_file_name : string
if len(os.args) < 2 {
- fmt.println("ERROR: No file submitted.")
+ fmt.println("No paths submitted.")
+ if os.is_file(HEADER_FIELDS_PATH) {
+ fmt.printfln("\"%v\" already exists.", HEADER_FIELDS_PATH)
+ } else {
+ os.write_entire_file(HEADER_FIELDS_PATH, transmute([]u8)HEADER_TEMPLATE)
+ fmt.printfln("Created \"%v\".", HEADER_FIELDS_PATH)
+ }
return
- } else {
- fmt.printf("Input path: {}\n", os.args[1])
- input_file_name = os.args[1]
}
+ fmt.printf("Input path: {}\n", os.args[1])
+ input_file_name = os.args[1]
+
+
path_info, error := os.stat(input_file_name)
@@ -71,22 +89,21 @@ main :: proc() {
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 {
+ try_os2 := walk_directory(path_info.fullpath, &file_count, &job_list, 1)
+ if len(job_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)
+ walk_directory_os2(path_info.fullpath, &file_count, &job_list, 1)
}
} else {
fmt.println("File submitted! Processing file...")
- append(&file_list, strings.clone(path_info.fullpath))
+ append(&job_list, strings.clone(path_info.fullpath))
}
- for file, f in file_list {
+ for file, f in job_list {
file_info, _ := os.stat(file)
fmt.printf("\nšŸ“„ File {}: {}\n", f+1, file_info.name)
@@ -99,8 +116,9 @@ main :: proc() {
render(parsed, output_name)
free_all(context.temp_allocator)
files_done += 1
+
}
- fmt.printf("\nCompleted {}/{} files.\n\n", files_done, len(file_list))
+ fmt.printf("\nCompleted {}/{} files.\n\n", files_done, len(job_list))
} else {
fmt.printf("ERROR could not get path info for: {}\n", input_file_name)
}
@@ -108,9 +126,19 @@ main :: proc() {
}
+parse :: proc(path : string, device : Device = .UNSET) -> (Report, bool) {
+ if os.is_dir(path) {
+ return parse_folder(path)
+ }
+ return parse_file(path, device)
+}
+parse_folder :: proc(path : string) -> (Report, bool) {
+ output : Report = {}
+ return output, false
+}
-parse :: proc(path : string, device : Device = .UNSET) -> (Report, bool) {
+parse_file :: proc(path : string, device : Device = .UNSET) -> (Report, bool) {
device := device
output : Report = {}
data, ok := os.read_entire_file(path, context.temp_allocator)
@@ -346,8 +374,6 @@ parse :: proc(path : string, device : Device = .UNSET) -> (Report, bool) {
}
}
- fmt.printf("\n")
-
@@ -666,16 +692,13 @@ render :: proc(report : Report, path : string) {
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 {
+walk_directory :: proc(path : string, file_number : ^int, job_list : ^[dynamic]string, depth : int = 0) -> bool {
handle, ok := os.open(path)
if ok != os.ERROR_NONE {
indent_by(depth)
@@ -692,6 +715,8 @@ walk_directory :: proc(path : string, file_number : ^int, file_list : ^[dynamic]
return true
}
+ wav_count := 0
+ has_csv := false
for file in files {
@@ -700,24 +725,38 @@ walk_directory :: proc(path : string, file_number : ^int, file_list : ^[dynamic]
if file.is_dir {
indent_by(depth)
fmt.printf("šŸ“ %s\n", file.name)
- walk_directory(full_path, file_number, file_list, depth+1) // Recurse
+ walk_directory(full_path, file_number, job_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"){
+ if extension == ".csv" {
indent_by(depth)
- fmt.printf("šŸ“„ %d %s\n", file_number^, file.name)
- append(file_list, strings.clone(file.fullpath))
+ fmt.printf("šŸ“„ [#%d] %s\n", file_number^, file.name)
+ append(job_list, strings.clone(file.fullpath))
file_number^ += 1
+ has_csv = true
+ }
+ if extension == ".wav" {
+ wav_count += 1
}
}
}
+ if wav_count>0 && !has_csv {
+ indent_by(depth+1)
+ if wav_count == 1 {
+ fmt.printf("šŸ’½ [#%d] 1 WAV file, without CSV.\n", file_number^)
+ } else {
+ fmt.printf("šŸ’½ [#%d] %d WAV files, without CSV.\n", file_number^, wav_count)
+ }
+ append(job_list, strings.clone(path))
+ file_number^ += 1
+ }
return false
}
-walk_directory_os2 :: proc(path : string, file_number : ^int, file_list : ^[dynamic]string, depth : int = 0) {
+walk_directory_os2 :: proc(path : string, file_number : ^int, job_list : ^[dynamic]string, depth : int = 0) {
handle, ok := os2.open(path)
if ok != os2.ERROR_NONE {
indent_by(depth)
@@ -733,6 +772,8 @@ walk_directory_os2 :: proc(path : string, file_number : ^int, file_list : ^[dyna
return
}
+ wav_count := 0
+ has_csv := false
for file in files {
@@ -741,18 +782,32 @@ walk_directory_os2 :: proc(path : string, file_number : ^int, file_list : ^[dyna
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
+ walk_directory_os2(full_path, file_number, job_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"){
+ if extension == ".csv" {
indent_by(depth)
- fmt.printf("šŸ“„ %d %s\n", file_number^, file.name)
- append(file_list, strings.clone(file.fullpath))
+ fmt.printf("šŸ“„ [#%d] %s\n", file_number^, file.name)
+ append(job_list, strings.clone(file.fullpath))
file_number^ += 1
+ has_csv = true
+ }
+ if extension == ".wav" {
+ wav_count += 1
}
}
}
+ if wav_count>0 && !has_csv {
+ indent_by(depth+1)
+ if wav_count == 1 {
+ fmt.printf("šŸ’½ [#%d] A WAV file, without CSV.\n", file_number^)
+ } else {
+ fmt.printf("šŸ’½ [#%d] %d WAV files, without CSV.\n", file_number^, wav_count)
+ }
+ append(job_list, strings.clone(path))
+ file_number^ += 1
+ }
} \ No newline at end of file
diff --git a/src/wav/wav.odin b/src/wav/wav.odin
index f2cf85c..01fbad1 100644
--- a/src/wav/wav.odin
+++ b/src/wav/wav.odin
@@ -18,10 +18,7 @@ Wav :: struct {
reported_size : u32,
// Metadata
- buf : []u8,
channel_names : []string,
- bext : []u8,
- ixml : string,
samples_since_midnight: u64,
timecode : Timecode,
tc_framerate : f32,
@@ -56,38 +53,44 @@ main :: proc() {
fmt.printf("\n\nf8 = %#v\n\n", f8)
}
-// TODO: Maybe split reading metadata into its own function call.
-
+/*
+Reads in the wav file data, including metadata.
+*/
read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) {
file : Wav
- file_ok : os.Error
- file.handle, file_ok = os.open(path)
+ load_err : os.Error
+ file.handle, load_err = os.open(path)
defer os.close(file.handle)
defer file.handle = 0
- assert(file_ok == os.General_Error.None)
+ if load_err != os.General_Error.None {
+ fmt.eprintln("ERROR %v: Unable to load file \"%v\"", load_err, path)
+ return {}, false
+ }
- file.buf = new([BUFFER_SIZE]u8)[:]
- defer delete(file.buf)
- os.read(file.handle, file.buf)
+ temp_buf := new([BUFFER_SIZE]u8)[:]
+ temp_bext : []u8
+ temp_ixml : string
+ defer delete(temp_buf)
+ os.read(file.handle, temp_buf)
head : int = 0
// RIFF header
- fmt.println(string(file.buf[0:4]))
- if string(file.buf[0:4]) != "RIFF" do return {}, false
+ fmt.println(string(temp_buf[0:4]))
+ if string(temp_buf[0:4]) != "RIFF" do return {}, false
head += 4
// Size
- file.reported_size = read_little_endian_u32(file.buf[head:head+4])
+ file.reported_size = read_little_endian_u32(temp_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
+
+ // Confirming again that this is a wave file
+ fmt.println(string(temp_buf[head:head+4]))
+ if string(temp_buf[head:head+4]) != "WAVE" do return {}, false
head += 4
fmt.println("\nChunks:\n")
@@ -95,10 +98,10 @@ read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) {
// Looping through chunks
null_chunks := 0
for _ in 0..<BUFFER_SIZE {
- chunk_id_raw := file.buf[head:head+4]
+ chunk_id_raw := temp_buf[head:head+4]
chunk_id := string(chunk_id_raw)
head += 4
- chunk_size := int(read_little_endian_u32(file.buf[head:head+4]))
+ chunk_size := int(read_little_endian_u32(temp_buf[head:head+4]))
head += 4
fmt.println(chunk_id, chunk_size,"\n-------------------------------------")
data_reached := false
@@ -110,21 +113,21 @@ read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) {
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)
+ temp_ixml = string(temp_buf[head:data_end])
+ //print_data = temp_ixml
null_chunks = 0
case "bext":
- print_data = string(file.buf[head:read_end])
- file.bext = file.buf[head:data_end]
+ temp_bext = temp_buf[head:data_end]
+ //print_data = string(temp_bext)
null_chunks = 0
case "fmt ":
- file.format = Audio_Format(read_little_endian_u16(file.buf[head:]))
+ file.format = Audio_Format(read_little_endian_u16(temp_buf[head:]))
fmt.println("Format:", file.format)
head += 2
- file.channels = int(read_little_endian_u16(file.buf[head:]))
+ file.channels = int(read_little_endian_u16(temp_buf[head:]))
fmt.println("Channels:", file.channels)
head += 2
- file.sample_rate = int(read_little_endian_u32(file.buf[head:]))
+ file.sample_rate = int(read_little_endian_u32(temp_buf[head:]))
fmt.println("Sample rate:", file.sample_rate)
head += 4
@@ -133,7 +136,7 @@ read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) {
// and are implied by the other fields.
head += 4 + 2
- file.bit_depth = int(read_little_endian_u16(file.buf[head:]))
+ file.bit_depth = int(read_little_endian_u16(temp_buf[head:]))
fmt.println("Bit depth:", file.bit_depth)
head += 2
head = data_end
@@ -167,104 +170,15 @@ read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) {
- // bext parsing
- if len(file.bext) > 0 {
-
- // Stripping null padding
- end := len(file.bext) - 1
- for end >= 0 && file.bext[end] == 0 {
- end -= 1
- }
- file.bext = file.bext[:end]
-
- naming_channel := 0
-
- description := string(file.bext[:256])
- for line in strings.split_lines(description) {
- if strings.starts_with(line, "sTRK") || strings.starts_with(line, "zTRK") {
- eq_index := strings.index(line, "=")
- file.channel_names[naming_channel] = strings.clone(line[eq_index+1:], allocator=allocator)
- naming_channel += 1
- }
- if strings.starts_with(line[1:], "TAKE=") {
- file.take, _ = strconv.parse_int(line[6:])
- }
- if strings.starts_with(line[1:], "CIRCLED=") {
- file.circled = line[9:] == "TRUE"
- }
- if strings.starts_with(line[1:], "SPEED=") {
- value := line[7:]
- num_end : int
- type_start : int
- for r, i in value {
- if !strings.contains_rune("1234567890.", r) && num_end == 0 {
- num_end = i
- }
- if r=='N' || r=='D' {
- type_start = i
- break
- }
- }
- file.tc_framerate, _ = strconv.parse_f32(value[:num_end])
- file.tc_dropframe = value[type_start:] != "ND"
-
- }
- // Only if ixml doesn't exist, so we don't allocate the note string twice.
- if file.ixml == "" {
- if strings.starts_with(line[1:], "NOTE=") {
- v := line[6:]
- if v != "" {
- file.note = strings.clone(v, allocator=allocator)
- }
- }
- }
- }
- head := 0
- fmt.printf("Description: \n%v\n", string(file.bext[head:256]))
- head += 256
- fmt.printf("Originator: %v\n", string(file.bext[head:head+32]))
- head += 32
- fmt.printf("Originator Reference: %v\n", string(file.bext[head:head+32]))
- head += 32
- fmt.printf("Origination Date: %v\n", string(file.bext[head:head+10]))
- head += 10
- fmt.printf("Origination Time: %v\n", string(file.bext[head:head+8]))
- head += 8
-
- file.samples_since_midnight = read_little_endian_u64(file.bext[head:head+8])
-
- seconds_since_midnight := file.samples_since_midnight / u64(file.sample_rate)
- file.timecode.hour = u8( seconds_since_midnight / 3600)
- file.timecode.minute = u8((seconds_since_midnight % 3600) / 60)
- file.timecode.second = u8( seconds_since_midnight % 60)
- file.timecode.frame = f32( f64(file.samples_since_midnight % u64(file.sample_rate) ) * f64(file.tc_framerate) / f64(file.sample_rate))
- fmt.printf("Time Reference: %v (Samples since midnight, source of timecode)\n", file.samples_since_midnight)
- fmt.printf(" %v seconds + %v samples\n", seconds_since_midnight, file.samples_since_midnight % u64(file.sample_rate))
- head += 8
-
- fmt.printf("Version: %v\n", read_little_endian_u16(file.bext[head:head+2]))
- head += 2
- fmt.printf("UMID Skipped.\n")
- head += 64
- fmt.printf("Skipped reserved nothingness.\n")
- head += 190
- fmt.printf("Coding history:\n%v\n", string(file.bext[head:]))
- }
- fmt.println()
-
- // just here to make some printing prettier
- file.bext = nil
-
-
// iXML Parsing
- if file.ixml != "" {
+ if temp_ixml != "" {
// Stripping null padding
- end := len(file.ixml) - 1
- for end >= 0 && file.ixml[end] == 0 {
+ end := len(temp_ixml) - 1
+ for end >= 0 && temp_ixml[end] == 0 {
end -= 1
}
- file.ixml = file.ixml[:end]
+ temp_ixml = temp_ixml[:end]
naming_channel := 0
@@ -406,14 +320,104 @@ read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) {
parsed_ixml : ^xml.Document
- parsed_ixml, _ = xml.parse(file.ixml, xml.Options{
+ parsed_ixml, _ = xml.parse(temp_ixml, xml.Options{
flags={.Ignore_Unsupported},
expected_doctype = "",
})
xml_recurse(parsed_ixml, 0, &file, &naming_channel, &interleave_set, allocator)
}
- file.buf = nil
+ // bext parsing
+ if len(temp_bext) > 0 {
+
+ // Stripping null padding
+ end := len(temp_bext) - 1
+ for end >= 0 && temp_bext[end] == 0 {
+ end -= 1
+ }
+ temp_bext = temp_bext[:end]
+
+ naming_channel := 0
+
+ description := string(temp_bext[:256])
+ for line in strings.split_lines(description) {
+
+ if file.channel_names[naming_channel] == "" &&
+ (strings.starts_with(line, "sTRK") || strings.starts_with(line, "zTRK")) {
+ eq_index := strings.index(line, "=")
+ file.channel_names[naming_channel] = strings.clone(line[eq_index+1:], allocator=allocator)
+ naming_channel += 1
+ }
+ if strings.starts_with(line[1:], "TAKE=") {
+ file.take, _ = strconv.parse_int(line[6:])
+ }
+ if strings.starts_with(line[1:], "CIRCLED=") {
+ file.circled = line[9:] == "TRUE"
+ }
+ if strings.starts_with(line[1:], "SPEED=") {
+ value := line[7:]
+ num_end : int
+ type_start : int
+ for r, i in value {
+ if !strings.contains_rune("1234567890.", r) && num_end == 0 {
+ num_end = i
+ }
+ if r=='N' || r=='D' {
+ type_start = i
+ break
+ }
+ }
+ file.tc_framerate, _ = strconv.parse_f32(value[:num_end])
+ file.tc_dropframe = value[type_start:] != "ND"
+
+ }
+ // Only if ixml doesn't exist, so we don't allocate the note string twice.
+ if file.note == "" {
+ if strings.starts_with(line[1:], "NOTE=") {
+ v := line[6:]
+ if v != "" {
+ file.note = strings.clone(v, allocator=allocator)
+ }
+ }
+ }
+ }
+ head := 0
+ fmt.printf("Description: \n%v\n", string(temp_bext[head:256]))
+ head += 256
+ fmt.printf("Originator: %v\n", string(temp_bext[head:head+32]))
+ head += 32
+ fmt.printf("Originator Reference: %v\n", string(temp_bext[head:head+32]))
+ head += 32
+ fmt.printf("Origination Date: %v\n", string(temp_bext[head:head+10]))
+ head += 10
+ fmt.printf("Origination Time: %v\n", string(temp_bext[head:head+8]))
+ head += 8
+
+ file.samples_since_midnight = read_little_endian_u64(temp_bext[head:head+8])
+
+ seconds_since_midnight := file.samples_since_midnight / u64(file.sample_rate)
+ file.timecode.hour = u8( seconds_since_midnight / 3600)
+ file.timecode.minute = u8((seconds_since_midnight % 3600) / 60)
+ file.timecode.second = u8( seconds_since_midnight % 60)
+ file.timecode.frame = f32( f64(file.samples_since_midnight % u64(file.sample_rate) ) * f64(file.tc_framerate) / f64(file.sample_rate))
+ fmt.printf("Time Reference: %v (Samples since midnight, source of timecode)\n", file.samples_since_midnight)
+ fmt.printf(" %v seconds + %v samples\n", seconds_since_midnight, file.samples_since_midnight % u64(file.sample_rate))
+ head += 8
+
+ fmt.printf("Version: %v\n", read_little_endian_u16(temp_bext[head:head+2]))
+ head += 2
+ fmt.printf("UMID Skipped.\n")
+ head += 64
+ fmt.printf("Skipped reserved nothingness.\n")
+ head += 190
+ fmt.printf("Coding history:\n%v\n", string(temp_bext[head:]))
+ }
+ fmt.println()
+
+ // just here to make some printing prettier
+ temp_bext = nil
+
+ temp_buf = nil
return file, true
}