diff options
| author | San Jacobs | 2025-12-10 16:18:04 +0100 |
|---|---|---|
| committer | San Jacobs | 2025-12-10 16:18:04 +0100 |
| commit | aca38ce5236e8997831cc37ba12f1ffa8e29aec2 (patch) | |
| tree | 734c7a40d3dda15e0acac4645b6bff112748de10 /src/wav | |
| parent | cd0191917c9591d18db9db6c812b0705ce55e591 (diff) | |
| download | better-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.odin | 347 |
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 { |