class EL_AUDIO_WAVE_HEADER

(source code)

description

Audio wave header

note
	description: "Audio wave header"

	author: "Finnian Reilly"
	copyright: "Copyright (c) 2001-2022 Finnian Reilly"
	contact: "finnian at eiffel hyphen loop dot com"

	license: "MIT license (See: en.wikipedia.org/wiki/MIT_License)"
	date: "2024-09-23 7:56:15 GMT (Monday 23rd September 2024)"
	revision: "7"

class
	EL_AUDIO_WAVE_HEADER

inherit
	SED_MEMORY_READER_WRITER
		rename
			make as make_writer
		export
			{NONE} all
		end

	EL_WAVEFORM_FORMAT_ABS
		export
			{NONE} all
			{ANY} ID_fmt
		end

	EL_MODULE_LIO

create
	make, make_empty, make_from_other

feature {NONE} -- Initialization

	make_empty
			--
		do
			make_with_buffer (create {MANAGED_POINTER}.make (Standard_header_size))
			create extra_header_chunks.make (7)
		end

	make_from_other (other: EL_AUDIO_WAVE_HEADER)
			--
		do
			make_empty
			make (other.num_channels, other.bits_per_sample // Bits_per_byte, other.samples_per_sec)
			extra_header_chunks.merge (other.extra_header_chunks)
		end

	make (a_num_channels, sample_bytes, a_samples_per_sec : INTEGER)
			--
		do
			make_empty
			num_channels := a_num_channels
			bits_per_sample := sample_bytes * Bits_per_byte
			samples_per_sec := a_samples_per_sec
			format := PCM_format
			set_block_align
			set_average_bytes_per_sec
			size := Standard_header_size
		end

feature -- Basic operations

	write_to_file (file: EL_WAVE_FILE)
			--
		require
			file_is_open: file.is_open_write
		do
			if not extra_header_chunks.is_empty then
				make_with_buffer (create {MANAGED_POINTER}.make (Standard_header_size + extra_header_chunks_size))
			end
			create_header
			file.go (0)
			file.put_managed_pointer (buffer, 0, buffer_size)
		end

	read_from_file (file: EL_WAVE_FILE)
			--
		require
			file_is_open: file.is_open_read
		local
			chunk: EL_WAV_FORMAT_CHUNK
			chunk_id: STRING
		do
			from
				chunk_id := read_chunk_id (file)
				chunk := read_chunk (chunk_id, file)
				size := size + chunk.size
			until
				chunk_id.is_equal (ID_data)
			loop
				if chunk_id.is_equal (ID_fmt) then
					set_format_from_chunk (chunk)
				end
				chunk_id := read_chunk_id (file)
				chunk := read_chunk (chunk_id, file)
				if not standard_header_IDs.has (chunk.id) then
					extra_header_chunks [chunk.id] := chunk
				end
				size := size + chunk.size
			end
			check
				is_data_chunk: chunk.id.is_equal (ID_data)
			end
			read_data_size (chunk)
		end

feature -- Element change

	set_bits_per_sample (a_bits_per_sample: like bits_per_sample)
			-- Set `bits_per_sample' to `a_bits_per_sample'.
		do
			bits_per_sample := a_bits_per_sample
		ensure
			bits_per_sample_assigned: bits_per_sample = a_bits_per_sample
		end

	set_samples_per_sec (a_samples_per_sec: like samples_per_sec)
			-- Set `samples_per_sec' to `a_samples_per_sec'.
		do
			samples_per_sec := a_samples_per_sec
		ensure
			samples_per_sec_assigned: samples_per_sec = a_samples_per_sec
		end

	set_num_channels (a_num_channels: like num_channels)
			-- Set `num_channels' to `a_num_channels'.
		do
			num_channels := a_num_channels
		ensure
			num_channels_assigned: num_channels = a_num_channels
		end

	set_sample_count (a_sample_count: INTEGER)
			--
		do
			sample_count := a_sample_count
			data_size := sample_count * block_align
			duration := (sample_count / samples_per_sec).truncated_to_real
		end

feature -- Access

	bits_per_sample: INTEGER

	block_align: INTEGER

	average_bytes_per_sec: INTEGER

	samples_per_sec: INTEGER

	num_channels: INTEGER

	format: INTEGER_16

	data_size: INTEGER
			-- Size in bytes

	sample_count: INTEGER

	duration: REAL

	size: INTEGER

	extra_header_chunks: EL_HASH_TABLE [EL_WAV_FORMAT_CHUNK, STRING]

feature {NONE} -- Implementation

	create_header
			--	Create a WAV File BigEndian  C structure
			-- (Standard Microsoft Header)

			--		struct soundhdr {
			--			char riff[4]; /* "RIFF" */
			--			long flength; /* file length in bytes */
			--			char wave[4]; /* "WAVE" */
			--			char fmt[4]; /* "fmt " */
			--			long block_size; /* in bytes (generally 16) */
			--			short format_tag; /* 1=PCM, 257=Mu-Law, 258=A-Law, 259=ADPCM */
			--			short num_chans; /* 1=mono, 2=stereo */
			--			long srate; /* Sampling rate in samples per second */
			--			long bytes_per_sec; /* bytes per second */
			--			short bytes_per_samp; /* 2=16-bit mono, 4=16-bit stereo */
			--			short bits_per_samp; /* Number of bits per sample */
			--			char data[4]; /* "data" */
			--			long dlength; /* data length in bytes (filelength - 44) */
			--		};
		require
			num_channels_set: num_channels /= 0
			bits_per_sample_set: bits_per_sample /= 0
			samples_per_sec_set: samples_per_sec /= 0
		do
			is_little_endian_storable := true
			count := 0
			set_for_writing
			set_block_align

			write_id 			(ID_RIFF)
			write_integer_32 	(Standard_header_size + data_size - 8 + extra_header_chunks_size)
			write_id			(ID_WAVE)
			write_id 			(ID_fmt)
			write_integer_32 	(Size_of_WAVE_chunk_section)
			write_integer_16 	(format)
			write_integer_16 	(num_channels.to_integer_16)
			write_integer_32 	(samples_per_sec)
			write_integer_32 	(average_bytes_per_sec)
			write_integer_16 	(block_align.to_integer_16)
			write_integer_16 	(bits_per_sample.to_integer_16)

			if not extra_header_chunks.is_empty then
				write_header_chunks
			end

			write_id 			(ID_data)
			write_integer_32 	(data_size)
		ensure
			buffer_position_valid: count = Standard_header_size + extra_header_chunks_size
		end

	write_header_chunks
			--
		local
			l_pos: INTEGER
			chunk_item: EL_WAV_FORMAT_CHUNK
			chunk_data: ARRAY [NATURAL_8]
		do
			from extra_header_chunks.start until extra_header_chunks.after loop
				chunk_item := extra_header_chunks.item_for_iteration
				write_id (chunk_item.id)
				write_integer_32 (chunk_item.data_size)
				l_pos := count
				chunk_data := chunk_item.buffer.read_array (
					0, chunk_item.data_size
				)
				if chunk_item.word_alignment_pad_byte = 1 then
					chunk_data.force (0, chunk_data.count + 1)
				end
				check_buffer (chunk_data.count)
				buffer.put_array (chunk_data, l_pos)
				l_pos := l_pos + chunk_data.count
				count := l_pos
				extra_header_chunks.forth
			end
		end

	write_id (id: STRING)
			-- Write `id'.
		require
			valid_id_size: id.count = 4
			is_ready: is_ready_for_writing
			id_not_void: id /= Void
		local
			i: INTEGER
		do
			from i := 1 until i > 4 loop
				write_character_8 (id.item (i))
				i := i + 1
			end
		end

feature -- Element change

	set_format_from_chunk (chunk: EL_WAV_FORMAT_CHUNK)
			--	Read WAV File BigEndian C structure

			-- struct fmt {
			--		char fmt[4]; 				// "fmt "
			--		long block_size; 			// in bytes (generally 16)
			--		short format_tag; 			// 1=PCM, 257=Mu-Law, 258=A-Law, 259=ADPCM
			--		short num_chans; 			// 1=mono, 2=stereo
			--		long srate; 				// Sampling rate in samples per second
			--		long bytes_per_sec; 		// bytes per second
			--		short bytes_per_samp; 		// 2=16-bit mono, 4=16-bit stereo
			--		short bits_per_samp; 		// Number of bits per sample
			-- };

		require
			is_format_chunk: chunk.id.is_equal (ID_fmt)
		do
			format := chunk.read_integer_16 -- Wave_format_pcm.to_integer_16
			num_channels := chunk.read_integer_16
			samples_per_sec := chunk.read_integer_32
			average_bytes_per_sec := chunk.read_integer_32
			block_align := chunk.read_integer_16
			bits_per_sample := chunk.read_integer_16
		end

feature {NONE} -- Implementation: read operations		

	read_data_size (chunk: EL_WAV_FORMAT_CHUNK)
			--	
		require
			is_format_chunk: chunk.id.is_equal (ID_data)
		do
			data_size := chunk.data_size
			sample_count := data_size // block_align
			duration := (sample_count / samples_per_sec).truncated_to_real
		end

	read_chunk_id (file: EL_WAVE_FILE): STRING
			--
		local
			i: INTEGER
		do
			create Result.make (Size_of_chunk_id)
			from i := 1 until i > Size_of_chunk_id loop
				file.read_character
				Result.append_character (file.last_character)
				i := i + 1
			end
		end

	read_chunk (an_id: STRING; file: EL_WAVE_FILE): EL_WAV_FORMAT_CHUNK
			--
		do
			create Result.make (an_id, file)
		end

	extra_header_chunks_size: INTEGER
			--
		do
			from
				extra_header_chunks.start
			until
				extra_header_chunks.after
			loop
				Result := Result + extra_header_chunks.item_for_iteration.size
				extra_header_chunks.forth
			end
		end

	set_block_align
			--
		do
			block_align := (bits_per_sample // 8) * num_channels
		end

	set_average_bytes_per_sec
			--
		do
			average_bytes_per_sec := block_align * samples_per_sec
		end

feature {NONE} -- Constants

	Standard_header_IDs: ARRAY [STRING]
			--
		once
			Result := << ID_data, ID_wave, ID_fmt >>
			Result.compare_objects
		end

	Bits_per_byte: INTEGER = 8

end