package flac import "core:bufio" import "core:crypto/legacy/md5" import "core:fmt" import "core:io" import "core:mem" import "core:os" Error :: union { io.Error, FlacError, } FlacError :: enum { Unable_To_Read_File, Invalid_Signature, Invalid_Sample_Rate, Missing_StreamInfo, Missing_Lead_Out_Track, Invalid_Coefficient_Precision, CRC_Mismatch, MD5_Mismatch, Unsupported_BPS, // TODO: Remove when implemented Unencoded_BPS_For_Fixed_Subframe_Is_Unsupported, // TODO: Remove when implemented Invalid_Residual_Coding_Method, Invalid_Block_Size, Bits_Per_Second_Mismatch, Negative_Coefficient_Bits_To_Shift, } /* Option: `.only_return_metadata` Returns all metadata of a FLAC stream without decoding the audio data. */ Option :: enum u8 { only_return_metadata, } Options :: bit_set[Option; u8] PictureType :: enum u32 { OTHER, FILE_ICON_32x32_PNG, OTHER_FILE_ICON, COVER_FRONT, COVER_BACK, LEAFLET, MEDIA, LEAD_ARTIST, ARTIST, CONDUCTOR, BAND, COMPOSER, LYRICIST, RECORDING_LOCATION, DURING_RECORDING, DURING_PERFORMANCE, VIDEO_SCREEN_CAPTURE, BRIGHT_COLORED_FISH, ILLUSTRATION, ARTIST_LOGO, PUBLISHER_LOGO, } Flac :: struct { metadata: FlacMetadata, // This holds the samples of the current frame. // TODO: Should this be deleted? every frame already holds its own samples data samples: [dynamic]i32, } FlacMetadata :: struct { total_samples: u64, bits_per_sample: u8, channels: u8, sample_rate: u32, expected_md5: [16]byte, calculated_md5: [16]byte, pictures: []Picture, vendor_name: string, vorbis_comments: []string, } Picture :: struct { type: PictureType, mimetype: string, description: string, width: u32, height: u32, depth: u32, num_colors: u32, data: []byte `fmt:"-"`, } Frame :: struct { channels: u8, sample_rate: u32, // A slice view of `Flac`'s `samples` field. // Contains the frame's decoded samples. // DO NOT FREE. samples: []i32, } @(private) decode_subframe :: proc(r: ^Reader, bps: u8, block_size: u16) -> (samples: []i32, err: Error) #no_bounds_check { subframe_samples := make([]i32, block_size) data := read_bits(r, 8) or_return subframe_type := (data & 0x7E) >> 1 low_3_bits := subframe_type & 7 has_wasted_bits := data & 1 == 1 wasted_bits := 0 if has_wasted_bits { for (read_bits(r, 1) or_return) != 1 { wasted_bits += 1 } wasted_bits += 1 } if subframe_type == 0 { // CONSTANT bits_to_read := int(bps) - wasted_bits sample := i32(read_bits(r, uint(bits_to_read)) or_return) if sample > 0 && (sample >> (uint(bits_to_read) - 1)) & 1 == 1 { sample = -(((~sample) & (i32(pow2(bits_to_read)) - 1)) + 1) } copy(subframe_samples, []i32{sample}) } else if subframe_type == 1 { // VERBATIM // TODO: this will break if bits per sample is anything BUT 16 if bps < 16 || bps > 17 { return nil, .Unsupported_BPS } for sample in 0..> 5) & 1 == 1 { // LPC sample_i := 0 predictor_order := (subframe_type & 0x1F) + 1 bits_to_read := int(bps) - wasted_bits for s in 0.. 0 && (unencoded_warmup_sample >> (uint(bits_to_read) - 1)) & 1 == 1 { unencoded_warmup_sample = -(((~unencoded_warmup_sample) & (i32(pow2(bits_to_read)) - 1)) + 1) } subframe_samples[s] = unencoded_warmup_sample } coeff_precision_minus_one := u8(read_bits(r, 4) or_return) if coeff_precision_minus_one == 0xF { return nil, .Invalid_Coefficient_Precision } coeff_precision := coeff_precision_minus_one + 1 coeff_bits_to_shift := i8(read_bits(r, 5) or_return) // NOTE: This is signed 2's complement so we check the MSB // NOTE: This is apparently supposed to never be a negative number according to the (new?) spec. // TODO: For now we just error if the shift value is negative. I have to find test cases to be sure. if (coeff_bits_to_shift >> (5 - 1)) & 1 == 1 { return nil, .Negative_Coefficient_Bits_To_Shift } coefficients := make([]i32, predictor_order) defer delete(coefficients) for i in 0.. 0 && (unencoded_coefficient >> (coeff_precision - 1)) & 1 == 1 { unencoded_coefficient = -(((~unencoded_coefficient) & (i32(pow2(coeff_precision)) - 1)) + 1) } coefficients[i] = unencoded_coefficient } residual_coding_method := ResidualCodingMethod(read_bits(r, 2) or_return) if residual_coding_method != .RICE && residual_coding_method != .RICE2 { return nil, .Invalid_Residual_Coding_Method } partition_order := read_bits(r, 4) or_return partitions := pow2(partition_order) lpc_partitions_loop: for i in 0.. 0 && (residual_sample_value >> (unencoded_bps - 1)) & 1 == 1 { residual_sample_value = -(((~residual_sample_value) & (i32(pow2(unencoded_bps)) - 1)) + 1) } // Restore sample values using the predictor and the residual values predictor_before_shift := 0 c := 0 #reverse for coefficient in coefficients { predictor_before_shift += int(coefficient) * int(subframe_samples[sample_i + c]) c += 1 } predictor := predictor_before_shift >> u8(coeff_bits_to_shift) sample := i32(predictor + int(residual_sample_value)) subframe_samples[sample_i + int(predictor_order)] = sample sample_i += 1 } } else { num_samples := 0 if partition_order == 0 { num_samples = int(block_size - u16(predictor_order)) } else if i != 0 { num_samples = int(block_size / u16(pow2(partition_order))) } else { num_samples = int((block_size / u16(pow2(partition_order))) - u16(predictor_order)) } // // Undo the RICE coding // for s in 0..> u8(coeff_bits_to_shift) sample := predictor + residual_sample_value subframe_samples[sample_i + int(predictor_order)] = i32(sample) sample_i += 1 } } } } else if (subframe_type >> 3) & 1 == 1 && low_3_bits <= 4 { // FIXED sample_i := 0 predictor_order := low_3_bits bits_to_read := int(bps) - wasted_bits // NOTE: samples can be negative for sample in 0.. 0 && (unencoded_warmup_sample >> (uint(bits_to_read) - 1)) & 1 == 1 { unencoded_warmup_sample = -(((~unencoded_warmup_sample) & (i32(pow2(bits_to_read)) - 1)) + 1) } subframe_samples[sample] = unencoded_warmup_sample } residual_coding_method := ResidualCodingMethod(read_bits(r, 2) or_return) if residual_coding_method != .RICE && residual_coding_method != .RICE2 { return nil, .Invalid_Residual_Coding_Method } partition_order := read_bits(r, 4) or_return partitions := pow2(partition_order) fixed_partitions_loop: for i in 0.. 0 && (residual_sample_value >> (unencoded_bps - 1)) & 1 == 1 { residual_sample_value = -(((~residual_sample_value) & (i8(pow2(unencoded_bps)) - 1)) + 1) } } return nil, .Unencoded_BPS_For_Fixed_Subframe_Is_Unsupported // TODO: calculate the actual sample value by using the residual AND the previous sample value // TODO: add the samples to something ?? } else { num_samples := 0 if partition_order == 0 { num_samples = int(block_size - u16(predictor_order)) } else if i != 0 { num_samples = int(block_size / u16(pow2(partition_order))) } else { num_samples = int((block_size / u16(pow2(partition_order))) - u16(predictor_order)) } // // Undo the RICE coding // for s in 0..> 1) right := (mid - side) >> 1 subframes[0][i] = left subframes[1][i] = i32(right) } case .STEREO_SIDE_RIGHT: for i in 0.. (out_flac: ^Flac, err: Error) { context.allocator = allocator /* Held in a local rather than in the named result: an Odin `defer` cannot change a named result, so the unwind below is able to free this but not to nil it out for the caller. Assigning it only on the successful return means every error path hands back nil instead of a freed pointer. */ flac: ^Flac header := read_data(r, FlacHeader) or_return if header.magic != FLAC_MAGIC { return nil, .Invalid_Signature } if (header.streaminfo.min_block_size < 16 || header.streaminfo.min_block_size > 65535) || (header.streaminfo.max_block_size < 16 || header.streaminfo.max_block_size > 65535) || (header.streaminfo.min_block_size > header.streaminfo.max_block_size) { return nil, .Invalid_Block_Size } sample_rate := header.streaminfo.sr_chan_bps_ts >> 44 num_channel_minus_one := (header.streaminfo.sr_chan_bps_ts >> 41) & 7 bits_per_sample_minus_one := (header.streaminfo.sr_chan_bps_ts >> 36) & 0x1F total_samples := header.streaminfo.sr_chan_bps_ts & 0xFFFFFFFFF streaminfo_header := MetadataBlockHeader { last_block = bool(header.streaminfo_header >> 31), type = BlockType((header.streaminfo_header >> 24) & 0x7F), length = u32(header.streaminfo_header & 0xFFFFFF), } if streaminfo_header.type != .STREAMINFO { return nil, .Missing_StreamInfo } if flac == nil { flac = new(Flac) } flac.metadata.channels = u8(num_channel_minus_one + 1) flac.metadata.sample_rate = u32(sample_rate) flac.metadata.bits_per_sample = u8(bits_per_sample_minus_one + 1) flac.metadata.total_samples = u64(total_samples) flac.metadata.expected_md5 = header.streaminfo.md5 pictures := make([dynamic]Picture, 0, 2, allocator) /* Everything allocated from here on hangs off `flac`, and a caller that gets an error back is handed a nil `flac` -- so it has nothing to pass to `destroy` and cannot clean any of this up itself. Unwind it here instead. Each block below therefore hands its allocations to `flac` or to `pictures` as soon as they exist, before the next read that could fail. */ defer if err != nil { for picture in pictures { delete(picture.mimetype) delete(picture.description) delete(picture.data) } delete(pictures) for comment in flac.metadata.vorbis_comments { delete(comment) } delete(flac.metadata.vorbis_comments) delete(flac.metadata.vendor_name) free(flac) } last_block := streaminfo_header.last_block for !last_block { md_block_hdr := read_data(r, u32be) or_return metadata_block_header := MetadataBlockHeader { last_block = bool(md_block_hdr >> 31), type = BlockType((md_block_hdr >> 24) & 0x7F), length = u32(md_block_hdr & 0xFFFFFF), } last_block = metadata_block_header.last_block switch metadata_block_header.type { case .STREAMINFO: // Do nothing, we already parse the mandatory streaminfo metadata case .PADDING: reader := cast(^bufio.Reader)r.data skipped := bufio.reader_discard(reader, int(metadata_block_header.length)) or_return case .APPLICATION: //app_id := read_slice(&r, 4) or_return //fmt.println("Found application with ID:", string(app_id)) //fmt.printfln("Skipping %d bytes of application metadata block", metadata_block_header.length) // TODO: handle the application data reader := cast(^bufio.Reader)r.data skipped := bufio.reader_discard(reader, int(metadata_block_header.length)) or_return case .SEEKTABLE: seekpoints := metadata_block_header.length / 18 //fmt.printfln("we have %d seekpoints", seekpoints) actual_size := 0 for i in 0.. (frame: Frame, err: Error) { // // Frame Header // context.allocator = allocator tee_r: io.Tee_Reader crc_buffer: Buffer buffer_init_allocator(&crc_buffer, 0, 128) defer buffer_destroy(&crc_buffer) crc := buffer_to_stream(&crc_buffer) r := &Reader{ // TODO: tee reader's docs say we should call io.destroy() when done with it. But the tee reader // doesn't implement the .Destroy method so it does absolutely nothing ?? Should ask about this in the discord. r = io.tee_reader_init(&tee_r, r, crc), buf = r.buf, x = r.x, n = r.n, } data := read_data(r, u16be) or_return sync_code := data >> 2 blocking_strategy := BlockingStrategy(data & 1) data = read_data(r, u16be) or_return block_size := data >> 12 sample_rate := SampleRate((data >> 8) & 0xF) channel_assignment := ChannelAssignment((data >> 4) & 0xF) sample_size := SampleSize((data >> 1) & 7) coded_num := decode_extended_utf8(r) or_return if blocking_strategy == .VARIABLE { // Sample number of first sample // MUST NOT be larger than 36 bits unencoded or 7 bytes encoded. // MUST be equal to the number of samples preceding the current frame. Otherwise seeking is not possible. // TODO: also write a check for the number of samples so we'll probably keep track of how many samples we decoded // maybe the number of decoded samples can be inferred by the number of frames ?? or just use the length of // the decoded_samples slice. if coded_num > 0x1000000000 { // TODO: Sample number can't be bigger than 36 bits } } else { // Frame number // MUST NOT be larger than 31 bits unencoded or 6 bytes encoded. // MUST be equal to the number of frames preceding the current frame. Otherwise seeking is not possible. // TODO: also write a check for the number of frames so we'll probably keep track of how many frames we decoded. if coded_num > 0x80000000 { // TODO: Frame number cannot be bigger than 31 bits } } actual_block_size: u16be switch block_size { case 1: actual_block_size = 192 case 2..=5: actual_block_size = 576 * auto_cast (pow2(block_size - 2)) case 6: actual_block_size = auto_cast (read_bits(r, 8) or_return) + 1 case 7: actual_block_size = (read_data(r, u16be) or_return) + 1 case 8..=15: actual_block_size = 256 * auto_cast (pow2(block_size - 8)) case: return {}, .Invalid_Block_Size } // TODO: Any frame but the last one MUST have a block size equal to or greater // than the minimum block size and MUST have a block size equal to or // less than the maximum block size. The last frame MUST have a block // size equal to or less than the maximum block size; it does not have // to comply to the minimum block size because the block size of that // frame must be able to accommodate the length of the audio data the // stream contains // //if actual_block_size < 15 && !last_frame { // TODO: block sizes less than 16 are only valid for the last frame and MUST NOT be used for any other frame. //} actual_sample_rate_in_Hz: u32 switch sample_rate { case .USE_STREAMINFO: actual_sample_rate_in_Hz = flac.metadata.sample_rate case ._88_2kHz: actual_sample_rate_in_Hz = 88200 case ._176_4kHz: actual_sample_rate_in_Hz = 176400 case ._192kHz: actual_sample_rate_in_Hz = 192000 case ._8kHz: actual_sample_rate_in_Hz = 8000 case ._16kHz: actual_sample_rate_in_Hz = 16000 case ._22_05kHz: actual_sample_rate_in_Hz = 22005 case ._24kHz: actual_sample_rate_in_Hz = 24000 case ._32kHz: actual_sample_rate_in_Hz = 32000 case ._44_1kHz: actual_sample_rate_in_Hz = 44100 case ._48kHz: actual_sample_rate_in_Hz = 48000 case ._96kHz: actual_sample_rate_in_Hz = 96000 case .USE_8_BITS_IN_kHz_FROM_HEADER_END: actual_sample_rate_in_Hz = auto_cast (read_bits(r, 8) or_return) * 1000 case .USE_16_BITS_IN_Hz_FROM_HEADER_END: actual_sample_rate_in_Hz = auto_cast (read_data(r, u16be) or_return) case .USE_16_BITS_IN_TENS_OF_Hz_FROM_HEADER_END: actual_sample_rate_in_Hz = auto_cast (read_data(r, u16be) or_return) * 10 case .INVALID: fallthrough case: return {}, .Invalid_Sample_Rate } // TODO: sample rate MUST NOT be 0 if the subframe contains audio. // a sample rate of 0 MAY be used when non-audio is represented. crc8_bytes := buffer_to_bytes(&crc_buffer) frame_header_crc := u8(read_bits(r, 8) or_return) calculated_header_crc := calculate_crc8(crc8_bytes) if frame_header_crc != calculated_header_crc { fmt.printfln("Expected frame header CRC: 0x%X, got: 0x%X", frame_header_crc, calculated_header_crc) return {}, .CRC_Mismatch } // // Subframes // bps: u8 switch sample_size { case .USE_STREAMINFO: bps = flac.metadata.bits_per_sample case ._8BPS: bps = 8 case ._12BPS: bps = 12 case ._16BPS: bps = 16 case ._20BPS: bps = 20 case ._24BPS: bps = 24 case ._32BPS: bps = 32 } if bps != flac.metadata.bits_per_sample { return {}, .Bits_Per_Second_Mismatch } frame_channels := flac.metadata.channels switch channel_assignment { case .MONO: frame_channels = 1 case ._3CHANNELS: frame_channels = 3 case ._4CHANNELS: frame_channels = 4 case ._5CHANNELS: frame_channels = 5 case ._6CHANNELS: frame_channels = 6 case ._7CHANNELS: frame_channels = 7 case ._8CHANNELS: frame_channels = 8 case ._2CHANNELS: fallthrough case .STEREO_LEFT_SIDE: fallthrough case .STEREO_SIDE_RIGHT: fallthrough case .STEREO_MID_SIDE: frame_channels = 2 } if frame_channels != flac.metadata.channels { // TODO: warning or error? // we have a faulty file that says it has 5 channels but subframes all say they got 1 // we also have an two uncommon files with increasing and decreasing number of channels // uncommon are supposed to be unusual but still valid FLAC files. why is that first one faulty then??? // so many questions... } subframes := make([][]i32, frame_channels) defer delete(subframes) for i in 0..> 8) & 0xFF) md5.update(md5_ctx, {first_byte, second_byte}) case 17..=24: first_byte := u8(sample & 0xFF) second_byte := u8((sample >> 8) & 0xFF) third_byte := u8((sample >> 16) & 0xFF) md5.update(md5_ctx, {first_byte, second_byte, third_byte}) case 25..=32: data := transmute([4]u8)sample md5.update(md5_ctx, data[:]) case: unreachable() } } } // `md5sum` writes the final MD5 checksum to the `flac` struct's `calculated_md5` field // and returns a MD5_Mismatch error in the case of a mismatch or nil. md5sum :: proc(md5_ctx: ^md5.Context, flac: ^Flac) -> Error { calculated_md5 := [16]byte{} md5.final(md5_ctx, calculated_md5[:]) flac.metadata.calculated_md5 = calculated_md5 if mem.compare(calculated_md5[:], flac.metadata.expected_md5[:]) != 0 { return .MD5_Mismatch } return nil } // `load_from_file` initializes a buffered reader for the file and reads all the metadata blocks. // Calls to `decode_next_frame()` MUST be made to decode the audio data. // This is the recommended way to read most FLAC files as it has a minimal memory overhead. load_from_file :: proc(filename: string, allocator := context.allocator) -> (flac: ^Flac, r: ^Reader, err: Error) { context.allocator = allocator file, open_err := os.open(filename) if open_err != nil { fmt.eprintln(open_err) return nil, nil, .Unable_To_Read_File } br := new(bufio.Reader) bufio.reader_init(br, os.to_stream(file)) reader := new(Reader) reader.r = bufio.reader_to_stream(br) reader.buf = 0 reader.x = 0 reader.n = 0 /* Without this an unreadable file in a scanned library burns an fd and the reader's 4 KB buffer on every attempt -- the caller is handed nil for both results and so has no handle to close the file with. `reader` is a local for the same reason `read_metadata` keeps one: a defer cannot nil out the named result, so `r` is only assigned on success. */ defer if err != nil { bufio.reader_destroy(br) free(br) free(reader) os.close(file) } flac = read_metadata(reader) or_return return flac, reader, nil } /* `destroy` cleans up and frees the memory allocated by all 3 loading functions. The returned reader from `load_from_file` MUST be passed to `destroy` in order to close the file handle. Both arguments may be nil, so it is safe to defer immediately after a load. --- The allocator that was used when loading the flac file MUST be passed to `destroy` in order to properly free the memory, otherwise a bad free may occur and crash the program. */ destroy :: proc(flac: ^Flac, r: ^Reader = nil, allocator := context.allocator) { context.allocator = allocator // Tolerates a nil `flac` so a caller can `defer destroy(...)` straight after // a load without first checking whether the load succeeded. if flac != nil { delete(flac.samples) for picture in flac.metadata.pictures { delete(picture.mimetype) delete(picture.data) delete(picture.description) } delete(flac.metadata.pictures) for comment in flac.metadata.vorbis_comments { delete(comment) } delete(flac.metadata.vorbis_comments) delete(flac.metadata.vendor_name) free(flac) } if r != nil { br := cast(^bufio.Reader)r.data closer, ok := io.to_closer(br.rd) io.close(closer) bufio.reader_destroy(br) free(br) free(r) } }