class EL_AUDIO_CLIP_SAVER

(source code)

description

Thread consumer for audio clips taken from a (thread product) work queue. Saves the clips in the temp directory with unique file names and puts the saved file path onto a (thread product) work queue for processing by another thread. Notifies a sound level listener of any audio clips which are silent (below the noise threshold)

note
	description: "[
		Thread consumer for audio clips taken from a (thread product) work queue.
		Saves the clips in the temp directory with unique file names and puts the saved file path
		onto a (thread product) work queue for processing by another thread.
		Notifies a sound level listener of any audio clips which are silent (below the noise threshold)
	]"

	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: "2023-03-10 10:10:55 GMT (Friday 10th March 2023)"
	revision: "6"

class
	EL_AUDIO_CLIP_SAVER

inherit
	EL_CONSUMER_THREAD [EL_WAVE_AUDIO_16_BIT_CLIP]
		rename
			consume_product as save_clip,
			product as audio_clip
		redefine
			on_start
		end

	EL_AUDIO_CLIP_SAVER_CONSTANTS
		undefine
			default_create, is_equal, copy
		end

	EL_MODULE_LOG

	EL_MODULE_LOG_MANAGER

	EL_MODULE_FILE_SYSTEM

	EL_MODULE_DIRECTORY

create
	make

feature {NONE} -- Initialization

	make (rms_energy: REAL)
			--
		do
			make_default
			noise_threshold := rms_energy
			create audio_file_processing_queue.make (0)
			log.put_new_line

			across Temporary_directory.files_with_extension ("wav") as wav_path loop
				if wav_path.item.base.starts_with (Clip_base_name) then
					File_system.remove_file (wav_path.item)
				end
			end
			create sample_count.make
		end

feature -- Access

	audio_file_processing_queue: EL_THREAD_PRODUCT_QUEUE [STRING]

	samples_recorded_count: INTEGER
			--
		do
			Result := sample_count.item
		end

feature -- Element change

	set_sound_level_listener (a_sound_level_listener: EL_SIGNAL_LEVEL_LISTENER)
			--
		require
			a_sound_level_listener_not_void: a_sound_level_listener /= Void
		do
			sound_level_listener := a_sound_level_listener
		end

	reset_sample_count
			--
		do
			sample_count.set_item (0)
		end

	set_signal_threshold (rms_energy: REAL)
			--
		do
			noise_threshold := rms_energy
		end

feature {NONE} -- Implementation

	save_clip
			--
		local
			clip_name: ZSTRING; audio_rms_energy: REAL
		do
			audio_rms_energy := audio_clip.rms_energy

			if sound_level_listener /= Void then
				sound_level_listener.set_signal_level (audio_rms_energy)
			end

			sample_count.add (audio_clip.sample_count)

			if audio_rms_energy > noise_threshold then
				log.enter ("save_clip")
				clip_count := clip_count + 1
				clip_name := unique_clip_name (clip_count)

				audio_clip.save (Directory.temporary + clip_name)

				log.put_string_field ("File name", clip_name)
				log.put_new_line

				audio_file_processing_queue.put (clip_name)
				log.exit
			else
				audio_file_processing_queue.put (Silent_clip_name)
			end
		end

	on_start
		do
			Log_manager.add_thread (Current)
		end

	unique_clip_name (n: INTEGER): ZSTRING
			--
		local
			n_string: STRING
		do
			Result := Clip_base_name.twin

			n_string := (n + Clip_no_base).out
			n_string.remove_head (1)

			Result.append_all_general (<< "-", n_string, ".wav" >>)

		end

feature {NONE} -- Internal attributes

	clip_count: INTEGER

	sound_level_listener: EL_SIGNAL_LEVEL_LISTENER

	sample_count: EL_MUTEX_NUMERIC [INTEGER]

	noise_threshold: REAL

feature -- Constants

	Temporary_directory: EL_DIRECTORY
			--
		once
			create Result.make (Directory.temporary)
		end

end