class EL_WAVE_FILE

(source code)

Client examples: MULTIMEDIA_AUTOTEST_APP

description

Wave file

note
	description: "Wave file"

	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-01-07 11:17:22 GMT (Sunday 7th January 2024)"
	revision: "7"

class
	EL_WAVE_FILE

inherit
	RAW_FILE
		rename
			close as close_file,
			make_open_write as file_make_open_write
		export
			{NONE} all
			{ANY}
				is_open_write, is_open_read,
				extendible, name, exists,
				open_write, open_append, open_read_write

			{EL_AUDIO_WAVE_HEADER}
				put_managed_pointer, go, read_character, last_character

			{EL_WAV_FORMAT_CHUNK, EL_AUDIO_WAVE_HEADER}
				read_integer, last_integer, read_to_managed_pointer, read_integer_8
		redefine
			make_open_read
		end

	EL_AUDIO_IO_MEDIUM
		rename
			make as make_sample_source,
			sample_count as sample_block_count,
			num_channels as num_sample_source_channels
		end

	EL_C_API

	EL_MODULE_LIO

create
	make_open_read, make_open_write

feature {NONE} -- Initialization

	make_open_write (file_name: FILE_PATH; a_num_channels, a_sample_bytes, a_samples_per_sec : INTEGER)
			--
		do
			file_make_open_write (file_name)
			create header.make (a_num_channels, a_sample_bytes, a_samples_per_sec)
		end

	make_open_read (file_name: STRING)
			--
		do
			Precursor (file_name)
			create header.make_empty
			header.read_from_file (Current)
			make_sample_source (header.num_channels)
			sample_block_count := header.sample_count
			duration := header.duration
			inspect header.bits_per_sample
				when 8 then
					create {EL_8_BIT_AUDIO_PCM_SAMPLE} internal_sample.make

				when 16 then
					create {EL_16_BIT_AUDIO_PCM_SAMPLE} internal_sample.make

				when 32 then
					create {EL_32_BIT_AUDIO_PCM_SAMPLE} internal_sample.make
			end
			create_last_sample_blocks
		end

	create_last_sample_blocks
			--
		do
			create last_sample_block_integer.make (1, num_channels)
			create last_sample_block_double.make (1, num_channels)
			create last_sample_block_real.make (1, num_channels)
		end

feature -- Basic operations

	process_at_sample_rate (sample_rate: INTEGER)
			-- Process sample_file with processor at sample_rate (samples/sec)
		local
			process_sample_count, i, channel: INTEGER
			unit_relative_pos: REAL
		do
			lio.enter ("process_at_sample_rate")
			process_sample_count := (duration * sample_rate).rounded.min (sample_block_count)

			do_all_channels (agent {EL_AUDIO_SAMPLE_PROCESSOR}.set_sample_count (sample_block_count))
			do_all_channels (agent {EL_AUDIO_SAMPLE_PROCESSOR}.set_samples_per_sec (samples_per_sec))

			go_sample (1)
			do_all_channels (agent {EL_AUDIO_SAMPLE_PROCESSOR}.on_start)

			from i := 1 until i > process_sample_count loop
				unit_relative_pos := (i / process_sample_count).truncated_to_real
				go_sample_at_relative_pos (unit_relative_pos)
				read_sample_block_integer
				from channel := 1 until channel > num_channels loop
					processors.item (channel).do_with_sample_at_time (
						last_sample_block_integer [channel], i, unit_relative_pos * duration
					)
					channel := channel + 1
				end
				i := i + 1
			end
			do_all_channels (agent {EL_AUDIO_SAMPLE_PROCESSOR}.on_finish)
			close
			lio.exit
		end

	process_all
			-- Process all sample_file
		require else
			is_open_read: is_open_read
		local
			channel, i: INTEGER
		do
			lio.enter ("process_all")

			do_all_channels (agent {EL_AUDIO_SAMPLE_PROCESSOR}.set_sample_count (sample_block_count))
			do_all_channels (agent {EL_AUDIO_SAMPLE_PROCESSOR}.set_samples_per_sec (samples_per_sec))

			go_sample (1)
			do_all_channels (agent {EL_AUDIO_SAMPLE_PROCESSOR}.on_start)

			from i := 1 until i > sample_block_count loop
				read_sample_block_integer
				from channel := 1 until channel > num_channels loop
					processors.item (channel).do_with_sample (last_sample_block_integer [channel], i)
					channel := channel + 1
				end
				i := i + 1
			end
			do_all_channels (agent {EL_AUDIO_SAMPLE_PROCESSOR}.on_finish)
			close
			lio.exit
		end

	close
			--
		do
			if sample_block_count /= header.sample_count then
				put_header
			end
			close_file
		end

	write_unit_double_samples_to_text_file (text_file_name: STRING)
			--
		local
			text_file: PLAIN_TEXT_FILE
			i, channel: INTEGER
			format_double: FORMAT_DOUBLE
		do
			create format_double.make (9, 6)
			create text_file.make_open_write (text_file_name)
			if is_closed then
				open_read
			end
			go_sample (1)
			from i := 1 until i > sample_block_count loop
				read_sample_block_double
				from channel := 1 until channel > num_channels loop
					text_file.put_string (format_double.formatted (last_sample_block_double [channel]))
					text_file.put_new_line
					channel := channel + 1
				end
				i := i + 1
			end
			text_file.close
			close
		end

feature -- Input

	read_sample_block_integer
			--
		local
			i: INTEGER
		do
			from i := 1 until i > num_channels loop
				read_to_managed_pointer (internal_sample, 0, internal_sample.c_size_of)
				last_sample_block_integer [i] := internal_sample.value
				i := i + 1
			end
		end

	read_sample_block_double
			--
		local
			i: INTEGER
		do
			from i := 1 until i > num_channels loop
				read_to_managed_pointer (internal_sample, 0, internal_sample.c_size_of)
				last_sample_block_double [i] := internal_sample.to_double_unit
				i := i + 1
			end
		end

	read_sample_block_real
			--
		local
			i: INTEGER
		do
			from i := 1 until i > num_channels loop
				read_to_managed_pointer (internal_sample, 0, internal_sample.c_size_of)
				last_sample_block_real [i] := internal_sample.to_real_unit
				i := i + 1
			end
		end

feature -- Access

	header: EL_AUDIO_WAVE_HEADER

	samples_per_sec: INTEGER
			--
		do
			Result := header.samples_per_sec
		end

	sample_block_position: INTEGER
			--
		do
			Result := (position - header.size) // sample_block_size + 1
		end

feature -- Cursor movement

	go_sample_at_relative_pos (unit_relative_pos: REAL)
			--
		require
			is_unit: unit_relative_pos.sign >= 0 and unit_relative_pos <= 1.0
		do
			go_sample (1 + (unit_relative_pos * (sample_block_count - 1)).rounded)
		end

	go_sample (index: INTEGER)
			--
		require
			header_not_void: header /= Void
			is_open_read: is_open_read
			valid_index: index >= 1
		do
			go (header.size + (index - 1) * sample_block_size )
		end

feature -- Access

	sample_block_count: INTEGER

	sample_block_size: INTEGER
			-- Combined size of samples for all channels
		do
			Result := internal_sample.c_size_of * num_channels
		end

	last_sample_block_integer: ARRAY [INTEGER]

	last_sample_block_double: ARRAY [DOUBLE]

	last_sample_block_real: ARRAY [REAL]

	unit_sample_block_array_double (lower, upper: INTEGER): EL_PCM_SAMPLE_BLOCK_ARRAY [DOUBLE]
			--
		local
			i: INTEGER
		do
			create Result.make (lower, upper, num_channels)

			go_sample (lower)
			from i := lower until i > upper loop
				read_sample_block_double
				Result.put (last_sample_block_double, i)
				i := i + 1
			end
		end

	unit_sample_array_double (channel, lower, upper: INTEGER): ARRAY [DOUBLE]
			--
		local
			i: INTEGER
		do
			create Result.make (lower, upper)

			go_sample (lower)
			from i := lower until i > upper loop
				read_sample_block_double
				Result.put (last_sample_block_double [channel], i)
				i := i + 1
			end
		end

	num_channels: INTEGER
			--
		do
			Result := header.num_channels
		end

feature -- Element change

	put_unit_sample_block_array_double (samples: EL_PCM_SAMPLE_BLOCK_ARRAY [DOUBLE])
			--
		require
			valid_number_of_channels: samples.num_channels = num_channels
			exists: exists
			is_writeable: is_open_write
		local
			i: INTEGER
		do
			go_sample (samples.lower)

			check
				right_position: header.size + (samples.lower - 1) * sample_block_size = position
			end

			from i := samples.lower until i > samples.upper loop
				put_unit_double_sample_block (samples.item (i))
				i := i + 1
			end
			sample_block_count := samples.upper
		end

	put_unit_sample_array_on_channel_double (samples: ARRAY [DOUBLE]; channel: INTEGER)
			--
		require
			exists: exists
			is_writeable: is_open_write
			valid_channel: channel <= num_channels
		local
			i: INTEGER
		do
			go_sample (samples.lower)

			check
				right_position: header.size + (samples.lower - 1) * sample_block_size = position
			end

			move ((channel - 1) * internal_sample.c_size_of)
			from i := samples.lower until i > samples.upper loop
				internal_sample.set_from_double_unit (samples [i])
				put_managed_pointer (internal_sample, 0, internal_sample.c_size_of)
				move ((num_channels - 1) * internal_sample.c_size_of)
				i := i + 1
			end
			sample_block_count := samples.upper
		end

	put_unit_real_sample_block (sample_block: ARRAY [REAL])
			--
		require
			valid_number_of_channels: sample_block.count = num_channels
		local
			channel: INTEGER
		do
			from channel := 1 until channel > num_channels loop
				internal_sample.set_from_real_unit (sample_block [channel])
				put_managed_pointer (internal_sample, 0, internal_sample.c_size_of)
				channel := channel + 1
			end
			sample_block_count := sample_block_count + 1
		end

	put_unit_double_sample_block (sample_block: ARRAY [DOUBLE])
			--
		require
			valid_number_of_channels: sample_block.count = num_channels
		local
			channel: INTEGER
		do
			from channel := 1 until channel > num_channels loop
				internal_sample.set_from_double_unit (sample_block [channel])
				put_managed_pointer (internal_sample, 0, internal_sample.c_size_of)
				channel := channel + 1
			end
			sample_block_count := sample_block_count + 1
		end

	put_sample_block (sample_block: ARRAY [INTEGER])
			--
		require
			valid_number_of_channels: sample_block.count = num_channels
		local
			channel: INTEGER
		do
			from channel := 1 until channel > num_channels loop
				internal_sample.set_value (sample_block [channel])
				put_managed_pointer (internal_sample, 0, internal_sample.c_size_of)
				channel := channel + 1
			end
			sample_block_count := sample_block_count + 1
		end

	put_normalised_real_samples_from_channel_files (real_sample_files: ARRAY [EL_MONO_UNITIZED_SAMPLE_FILE])
			--
		require
			one_for_each_channels: real_sample_files.count = num_channels
			real_sample_file_is_open:
				real_sample_files.linear_representation.for_all (agent {EL_MONO_UNITIZED_SAMPLE_FILE}.is_open_read)
			extendible: extendible
		local
			i, channel: INTEGER
			l_sample_block: ARRAY [REAL]
		do
			lio.enter ("write_normalised_real_samples")
			create l_sample_block.make (1, real_sample_files.count)
			open_append
			from i := 1 until i > sample_block_count loop
				from channel := 1 until channel > num_channels loop
					real_sample_files.item (channel).read_real
					l_sample_block [channel] := (real_sample_files.item (channel).last_normalised_sample).truncated_to_real
					channel := channel + 1
				end
				put_unit_real_sample_block (l_sample_block)
				i := i + 1
			end
			close
			lio.put_integer_field ("sample_block_count", sample_block_count)
			lio.put_new_line
			lio.exit
		end

	put_other_file (other: EL_WAVE_FILE)
			--
		require
			other_is_open_read: other.is_open_read
		local
			i: INTEGER
		do
			other.go_sample (1)
			from i := 1 until i > other.sample_block_count loop
				other.read_sample_block_integer
				put_sample_block (other.last_sample_block_integer)
				i := i + 1
			end
		end

	put_raw_sample_data (sample_data: MANAGED_POINTER)
			--
		do
			put_managed_pointer (sample_data, 0, sample_data.count)
			sample_block_count := sample_block_count + sample_data.count // (sample_bytes * num_channels)
		end

	put_header
			--
		require
			is_open_read_write: is_open_write
		do
			lio.enter ("write_header")
			start
			header.set_sample_count (sample_block_count)
			header.write_to_file (Current)
			lio.exit
		end

feature {EL_WAVE_FILE} -- Implementation

	internal_sample: EL_AUDIO_PCM_SAMPLE
		-- Current internal_sample

	sample_bytes: INTEGER
			--
		do
			Result := internal_sample.c_size_of
		end

end