aboutsummaryrefslogtreecommitdiff
path: root/src/wav
diff options
context:
space:
mode:
authorSan Jacobs2025-12-10 16:18:04 +0100
committerSan Jacobs2025-12-10 16:18:04 +0100
commitaca38ce5236e8997831cc37ba12f1ffa8e29aec2 (patch)
tree734c7a40d3dda15e0acac4645b6bff112748de10 /src/wav
parentcd0191917c9591d18db9db6c812b0705ce55e591 (diff)
downloadbetter-report-aca38ce5236e8997831cc37ba12f1ffa8e29aec2.tar.gz
better-report-aca38ce5236e8997831cc37ba12f1ffa8e29aec2.tar.bz2
better-report-aca38ce5236e8997831cc37ba12f1ffa8e29aec2.zip
Big parsing progress
Diffstat (limited to 'src/wav')
-rw-r--r--src/wav/wav.odin347
1 files changed, 299 insertions, 48 deletions
diff --git a/src/wav/wav.odin b/src/wav/wav.odin
index 500122e..f2cf85c 100644
--- a/src/wav/wav.odin
+++ b/src/wav/wav.odin
@@ -3,6 +3,7 @@ package wav
import "core:fmt"
import "core:math"
import "core:strings"
+import "core:strconv"
import "core:os"
import "core:encoding/xml"
@@ -19,19 +20,38 @@ Wav :: struct {
// Metadata
buf : []u8,
channel_names : []string,
- bext : string,
+ bext : []u8,
ixml : string,
+ samples_since_midnight: u64,
+ timecode : Timecode,
+ tc_framerate : f32,
+ tc_dropframe : bool,
+ ubits : [8]u8,
+ take : int,
+ project : string,
+ scene : string,
+ note : string,
+ tape : string,
+ circled : bool,
}
Audio_Format :: enum {
PCM = 1,
FLOAT = 3,
}
+Timecode :: struct {
+ hour : u8,
+ minute : u8,
+ second : u8,
+ frame : f32,
+}
BUFFER_SIZE :: 1<<15
main :: proc() {
enok, enok_ok := read_wav("test/ENOKS-BIRHTDAYT02.WAV", context.temp_allocator)
fmt.printf("\n\nenok = %#v\n\n", enok)
+ prins, prins_ok := read_wav("test/KRONPRINS01T01.wav", context.temp_allocator)
+ fmt.printf("\n\nprins = %#v\n\n", prins)
f8, f8_ok := read_wav("test/F8-SL098-T001.WAV", context.temp_allocator)
fmt.printf("\n\nf8 = %#v\n\n", f8)
}
@@ -44,6 +64,7 @@ read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) {
file_ok : os.Error
file.handle, file_ok = os.open(path)
defer os.close(file.handle)
+ defer file.handle = 0
assert(file_ok == os.General_Error.None)
file.buf = new([BUFFER_SIZE]u8)[:]
@@ -72,12 +93,16 @@ read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) {
fmt.println("\nChunks:\n")
// Looping through chunks
+ null_chunks := 0
for _ in 0..<BUFFER_SIZE {
- chunk_id := string(file.buf[head:head+4])
+ chunk_id_raw := file.buf[head:head+4]
+ chunk_id := string(chunk_id_raw)
head += 4
chunk_size := int(read_little_endian_u32(file.buf[head:head+4]))
head += 4
- fmt.println(chunk_id,"\n-------------------------------------")
+ fmt.println(chunk_id, chunk_size,"\n-------------------------------------")
+ data_reached := false
+ next_chunk_start := head + chunk_size
if head+chunk_size < BUFFER_SIZE {
print_data : string
@@ -85,17 +110,22 @@ 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])
+ //print_data = string(file.buf[head:read_end])
file.ixml = strings.clone(string(file.buf[head:data_end]), allocator=allocator)
+ null_chunks = 0
case "bext":
print_data = string(file.buf[head:read_end])
- file.bext = strings.clone(string(file.buf[head:data_end]), allocator=allocator)
+ file.bext = file.buf[head:data_end]
+ null_chunks = 0
case "fmt ":
file.format = Audio_Format(read_little_endian_u16(file.buf[head:]))
+ fmt.println("Format:", file.format)
head += 2
file.channels = int(read_little_endian_u16(file.buf[head:]))
+ fmt.println("Channels:", file.channels)
head += 2
file.sample_rate = int(read_little_endian_u32(file.buf[head:]))
+ fmt.println("Sample rate:", file.sample_rate)
head += 4
// Skipping byte rate and block align.
@@ -104,79 +134,300 @@ read_wav :: proc(path : string, allocator:=context.allocator) -> (Wav, bool) {
head += 4 + 2
file.bit_depth = int(read_little_endian_u16(file.buf[head:]))
+ fmt.println("Bit depth:", file.bit_depth)
head += 2
+ head = data_end
+ null_chunks = 0
+ case "data":
+ data_reached = true
+ case "\x00\x00\x00\x00":
+ if chunk_size == 0 {
+ null_chunks += 1
+ }
}
fmt.println(print_data, "\n")
} else {
+ fmt.println("End of buffer reached.")
break
}
- head += chunk_size
+ head = next_chunk_start
+
+ if null_chunks > 3 {
+ fmt.println("Got more than 3 null chunks in a row. Quitting parse.")
+ break
+ }
+ if data_reached {
+ fmt.println("Data reached.")
+ }
}
file.channel_names = make([]string, file.channels, allocator=allocator)
- naming_channel := 0
- for line in strings.split_lines(file.bext) {
- 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
+
+
+ // 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()
- // iXML Parsing
+ // just here to make some printing prettier
+ file.bext = nil
- parsed_ixml : ^xml.Document
+ // iXML Parsing
if file.ixml != "" {
+
+ // Stripping null padding
+ end := len(file.ixml) - 1
+ for end >= 0 && file.ixml[end] == 0 {
+ end -= 1
+ }
+ file.ixml = file.ixml[:end]
+
+
+ naming_channel := 0
+ /*
+ interleave_set is here because we don't want to overwrite naming_channel
+ with a number from CHANNEL_INDEX, if we've already set it with INTERLEAVE_INDEX.
+ INTERLEAVE_INDEX is the number that actually tells you which channel the name
+ belongs to. CHANNEL_INDEX is to be treated as a backup.
+ */
+ interleave_set := false
+
+ xml_recurse :: proc(doc: ^xml.Document, element_id: xml.Element_ID, file: ^Wav, naming_channel: ^int, interleave_set: ^bool, allocator:=context.allocator, indent := 0) {
+
+ naming_channel := naming_channel
+ interleave_set := interleave_set
+
+
+ tab :: proc(indent: int) {
+ for _ in 0..=indent {
+ fmt.printf("\t")
+ }
+ }
+
+ fmt.printf("\n")
+ tab(indent)
+
+ element := doc.elements[element_id]
+
+ if element.kind == .Element {
+ fmt.printf("<%v>", element.ident)
+
+ if len(element.value) > 0 {
+ value := element.value[0]
+ switch element.ident {
+ case "TRACK":
+ interleave_set^ = false
+ case "CHANNEL_INDEX":
+ if !interleave_set^ {
+ switch v in value {
+ case string:
+ naming_channel^, _ = strconv.parse_int(v)
+ case u32:
+ naming_channel^ = int(v)
+ }
+ }
+ naming_channel^ -= 1
+ case "INTERLEAVE_INDEX":
+ interleave_set^ = true
+ switch v in value {
+ case string:
+ naming_channel^, _ = strconv.parse_int(v)
+ case u32:
+ naming_channel^ = int(v)
+ }
+ naming_channel^ -= 1
+ case "NAME":
+ #partial switch v in value {
+ case string:
+ file.channel_names[naming_channel^] = strings.clone(v, allocator=allocator)
+ }
+ case "UBITS":
+ #partial switch v in value {
+ case string:
+ for r, i in v {
+ file.ubits[i] = u8(r) - u8('0')
+ }
+ }
+ case "TAKE":
+ #partial switch v in value {
+ case string:
+ file.take, _ = strconv.parse_int(v)
+ }
+ case "SCENE":
+ #partial switch v in value {
+ case string:
+ file.scene = strings.clone(v, allocator=allocator)
+ }
+ case "PROJECT":
+ #partial switch v in value {
+ case string:
+ file.project = strings.clone(v, allocator=allocator)
+ }
+ case "TAPE":
+ #partial switch v in value {
+ case string:
+ file.tape = strings.clone(v, allocator=allocator)
+ }
+ case "CIRCLED":
+ #partial switch v in value {
+ case string:
+ file.circled = v == "TRUE"
+ }
+ case "TIMECODE_FLAG":
+ #partial switch v in value {
+ case string:
+ file.tc_dropframe = v != "NDF"
+ }
+ case "TIMECODE_RATE":
+ #partial switch v in value {
+ case string:
+ end : int
+ for r, i in v {
+ if r == '/' {
+ end = i
+ break
+ }
+ }
+ file.tc_framerate, _ = strconv.parse_f32(v[:end])
+ }
+ case "NOTE":
+ #partial switch v in value {
+ case string:
+ if v != "" {
+ file.note = strings.clone(v, allocator=allocator)
+ }
+ }
+ }
+ }
+
+ for value in element.value {
+ switch v in value {
+ case string:
+ fmt.printf(": %v", v)
+ case xml.Element_ID:
+ xml_recurse(doc, v, file, naming_channel, interleave_set, allocator, indent + 1)
+ }
+ }
+
+ for attr in element.attribs {
+ tab(indent + 1)
+ fmt.printf("[Attr] %v: %v\n", attr.key, attr.val)
+ }
+ } else if element.kind == .Comment {
+ fmt.printf("[COMMENT] %v\n", element.value)
+ }
+
+ return
+ }
+
+
+ parsed_ixml : ^xml.Document
parsed_ixml, _ = xml.parse(file.ixml, xml.Options{
flags={.Ignore_Unsupported},
expected_doctype = "",
})
+ xml_recurse(parsed_ixml, 0, &file, &naming_channel, &interleave_set, allocator)
}
- xml_recurse(parsed_ixml, 0)
-
- delete(file.buf)
file.buf = nil
return file, true
}
-xml_recurse :: proc(doc: ^xml.Document, element_id: xml.Element_ID, indent := 0) {
- tab :: proc(indent: int) {
- for _ in 0..=indent {
- fmt.printf("\t")
- }
- }
-
- tab(indent)
-
- element := doc.elements[element_id]
-
- if element.kind == .Element {
- fmt.printf("<%v>\n", element.ident)
- for value in element.value {
- switch v in value {
- case string:
- tab(indent + 1)
- fmt.printf("[Value] %v\n", v)
- case xml.Element_ID:
- xml_recurse(doc, v, indent + 1)
- }
- }
-
- for attr in element.attribs {
- tab(indent + 1)
- fmt.printf("[Attr] %v: %v\n", attr.key, attr.val)
- }
- } else if element.kind == .Comment {
- fmt.printf("[COMMENT] %v\n", element.value)
- }
- return
+read_little_endian_u64 :: proc(x : []u8) -> u64 {
+ return u64(x[0]) |
+ u64(x[1]) << 8 |
+ u64(x[2]) << 16 |
+ u64(x[3]) << 24 |
+ u64(x[4]) << 32 |
+ u64(x[5]) << 40 |
+ u64(x[6]) << 48 |
+ u64(x[7]) << 56
}
read_little_endian_u32 :: proc(x : []u8) -> u32 {