From 9222181b3f5413cb5667c476f81fc21908a3b026 Mon Sep 17 00:00:00 2001 From: Andreas Zecher Date: Mon, 9 Mar 2026 08:53:05 +0100 Subject: [PATCH] Introduce AudioFormat value object to reduce parameter counts Groups file_type, sample_rate, and bit_depth into a single AudioFormat Data object. Adds Audio#format and Device#target_format as named constructors, reducing FileConverter#convert from 9 to 5 params and UI#conversion_progress from 6 to 2 params. Co-Authored-By: Claude Sonnet 4.6 --- lib/wavesync.rb | 1 + lib/wavesync/audio.rb | 10 +++++++++- lib/wavesync/audio_format.rb | 13 +++++++++++++ lib/wavesync/device.rb | 8 ++++++++ lib/wavesync/file_converter.rb | 17 ++++++++--------- lib/wavesync/scanner.rb | 21 +++++++-------------- lib/wavesync/ui.rb | 19 ++++++++----------- test/wavesync/audio_test.rb | 9 +++++++++ test/wavesync/device_test.rb | 19 +++++++++++++++++++ test/wavesync/file_converter_test.rb | 13 ++++++++++--- 10 files changed, 92 insertions(+), 38 deletions(-) create mode 100644 lib/wavesync/audio_format.rb diff --git a/lib/wavesync.rb b/lib/wavesync.rb index 38a3a36..92b046b 100644 --- a/lib/wavesync.rb +++ b/lib/wavesync.rb @@ -4,6 +4,7 @@ module Wavesync end require 'wavesync/acid_chunk' +require 'wavesync/audio_format' require 'wavesync/audio' require 'wavesync/config' require 'wavesync/device' diff --git a/lib/wavesync/audio.rb b/lib/wavesync/audio.rb index 290433d..4ab3098 100644 --- a/lib/wavesync/audio.rb +++ b/lib/wavesync/audio.rb @@ -23,7 +23,7 @@ def initialize(file_path) end def sample_rate - @audio.audio_sample_rate + @sample_rate ||= @audio.audio_sample_rate end def bit_depth @@ -32,6 +32,14 @@ def bit_depth attr_reader :bpm + def format + AudioFormat.new( + file_type: @file_ext.delete_prefix('.'), + sample_rate: sample_rate, + bit_depth: bit_depth + ) + end + def write_bpm(bpm) case @file_ext when '.m4a' diff --git a/lib/wavesync/audio_format.rb b/lib/wavesync/audio_format.rb new file mode 100644 index 0000000..0e555b5 --- /dev/null +++ b/lib/wavesync/audio_format.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +module Wavesync + AudioFormat = Data.define(:file_type, :sample_rate, :bit_depth) do + def merge(other) + with( + file_type: other.file_type || file_type, + sample_rate: other.sample_rate || sample_rate, + bit_depth: other.bit_depth || bit_depth + ) + end + end +end diff --git a/lib/wavesync/device.rb b/lib/wavesync/device.rb index b68293d..1f38c8a 100644 --- a/lib/wavesync/device.rb +++ b/lib/wavesync/device.rb @@ -38,6 +38,14 @@ def self.load_from_yaml end end + def target_format(source_format, source_file_path) + AudioFormat.new( + file_type: target_file_type(source_file_path), + sample_rate: target_sample_rate(source_format.sample_rate), + bit_depth: target_bit_depth(source_format.bit_depth) + ) + end + def target_file_type(source_file_path) file_extension = File.extname(source_file_path).downcase[1..] return nil if file_types.include?(file_extension) diff --git a/lib/wavesync/file_converter.rb b/lib/wavesync/file_converter.rb index 23e37e6..4a7b32d 100644 --- a/lib/wavesync/file_converter.rb +++ b/lib/wavesync/file_converter.rb @@ -2,17 +2,16 @@ module Wavesync class FileConverter - def convert(audio, source_file_path, path_resolver, target_file_type, _source_sample_rate, - target_sample_rate, source_bit_depth, target_bit_depth, &before_transcode) - return false unless target_file_type || target_sample_rate || target_bit_depth + def convert(audio, source_file_path, path_resolver, source_format, target_format, &before_transcode) + return false unless target_format.file_type || target_format.sample_rate || target_format.bit_depth - target_path = path_resolver.resolve(source_file_path, audio, target_file_type: target_file_type) + target_path = path_resolver.resolve(source_file_path, audio, target_file_type: target_format.file_type) files_to_cleanup = path_resolver.find_files_to_cleanup(target_path, audio) files_to_cleanup.each { |file| FileUtils.rm_f(file) } - if target_file_type - source_converted_path = Pathname(source_file_path).sub_ext(".#{target_file_type}") + if target_format.file_type + source_converted_path = Pathname(source_file_path).sub_ext(".#{target_format.file_type}") return false if source_converted_path.exist? end @@ -21,9 +20,9 @@ def convert(audio, source_file_path, path_resolver, target_file_type, _source_sa target_path.dirname.mkpath before_transcode&.call - audio.transcode(target_path.to_s, target_sample_rate: target_sample_rate, - target_file_type: target_file_type, - target_bit_depth: target_bit_depth || source_bit_depth) + audio.transcode(target_path.to_s, target_sample_rate: target_format.sample_rate, + target_file_type: target_format.file_type, + target_bit_depth: target_format.bit_depth || source_format.bit_depth) true end diff --git a/lib/wavesync/scanner.rb b/lib/wavesync/scanner.rb index 0d2893a..27b0c64 100644 --- a/lib/wavesync/scanner.rb +++ b/lib/wavesync/scanner.rb @@ -24,26 +24,19 @@ def sync(target_library_path, device) audio = Audio.new(file) @ui.bpm(audio.bpm) - file_type = device.target_file_type(file) - source_sample_rate = audio.sample_rate - source_bit_depth = audio.bit_depth - target_sample_rate = device.target_sample_rate(source_sample_rate) - target_bit_depth = device.target_bit_depth(source_bit_depth) + source_format = audio.format + target_format = device.target_format(source_format, file) @ui.file_progress(file) - if file_type || target_sample_rate || target_bit_depth - converted = @converter.convert(audio, file, path_resolver, file_type, source_sample_rate, - target_sample_rate, source_bit_depth, target_bit_depth) do - source_file_type = File.extname(file).delete_prefix('.') - @ui.conversion_progress(source_sample_rate, target_sample_rate, source_bit_depth, - source_file_type, file_type, target_bit_depth) + if target_format.file_type || target_format.sample_rate || target_format.bit_depth + converted = @converter.convert(audio, file, path_resolver, source_format, target_format) do + @ui.conversion_progress(source_format, target_format) end - target_path = path_resolver.resolve(file, audio, target_file_type: file_type) + target_path = path_resolver.resolve(file, audio, target_file_type: target_format.file_type) else copied = copy_file(audio, file, path_resolver) - source_file_type = File.extname(file).delete_prefix('.') - @ui.copy(source_sample_rate, source_bit_depth, source_file_type) + @ui.copy(source_format) target_path = path_resolver.resolve(file, audio) end diff --git a/lib/wavesync/ui.rb b/lib/wavesync/ui.rb index fd7f101..004146c 100644 --- a/lib/wavesync/ui.rb +++ b/lib/wavesync/ui.rb @@ -36,25 +36,22 @@ def sync_progress(index, total_count, device) sticky(parts.join(' '), 0) end - def conversion_progress(source_sample_rate, target_sample_rate, source_bit_depth, source_file_type, - target_file_type, target_bit_depth = nil) - target_sample_rate = source_sample_rate if target_sample_rate.nil? - target_file_type = source_file_type if target_file_type.nil? - target_bit_depth = source_bit_depth if target_bit_depth.nil? + def conversion_progress(source_format, target_format) + effective = source_format.merge(target_format) - source_info = audio_info(source_sample_rate, source_bit_depth) - target_info = audio_info(target_sample_rate, target_bit_depth) + source_info = audio_info(source_format.sample_rate, source_format.bit_depth) + target_info = audio_info(effective.sample_rate, effective.bit_depth) formatted_line = in_color( - "Converting #{source_file_type} (#{source_info}) ⇢ #{target_file_type} (#{target_info})", :highlight + "Converting #{source_format.file_type} (#{source_info}) ⇢ #{effective.file_type} (#{target_info})", :highlight ) sticky(formatted_line, 3) end - def copy(source_sample_rate, source_bit_depth, source_file_type) - info = audio_info(source_sample_rate, source_bit_depth) + def copy(source_format) + info = audio_info(source_format.sample_rate, source_format.bit_depth) - sticky(in_color("Copying #{source_file_type} (#{info})", :highlight), 3) + sticky(in_color("Copying #{source_format.file_type} (#{info})", :highlight), 3) end def skip diff --git a/test/wavesync/audio_test.rb b/test/wavesync/audio_test.rb index 6e04794..76bdfda 100644 --- a/test/wavesync/audio_test.rb +++ b/test/wavesync/audio_test.rb @@ -3,6 +3,7 @@ require 'tempfile' require 'fileutils' require_relative 'test_case' +require_relative '../../lib/wavesync/audio_format' require_relative '../../lib/wavesync/audio' require_relative '../../lib/wavesync/acid_chunk' @@ -87,6 +88,14 @@ class AudioTest < Wavesync::TestCase end end + test '#format returns an AudioFormat with file type, sample rate, and bit depth' do + format = audio('44100_16.wav').format + assert_instance_of AudioFormat, format + assert_equal 'wav', format.file_type + assert_equal 44_100, format.sample_rate + assert_equal 16, format.bit_depth + end + test 'find_all returns files for all supported extensions' do files = Audio.find_all(FIXTURES_PATH) exts = files.map { |f| File.extname(f).downcase }.uniq.sort diff --git a/test/wavesync/device_test.rb b/test/wavesync/device_test.rb index 28eeb59..7b2bda4 100644 --- a/test/wavesync/device_test.rb +++ b/test/wavesync/device_test.rb @@ -2,6 +2,7 @@ require 'yaml' require_relative 'test_case' +require_relative '../../lib/wavesync/audio_format' require_relative '../../lib/wavesync/device' module Wavesync @@ -95,6 +96,24 @@ class DeviceTest < Wavesync::TestCase assert_nil tp7.target_bit_depth(24) end + test '.target_format returns all nils when source format is fully supported' do + tp7 = Device.find_by(name: 'TP-7') + source_format = AudioFormat.new(file_type: 'wav', sample_rate: 44_100, bit_depth: 24) + target_format = tp7.target_format(source_format, 'song.wav') + assert_nil target_format.file_type + assert_nil target_format.sample_rate + assert_nil target_format.bit_depth + end + + test '.target_format returns converted values when source format is unsupported' do + octatrack = Device.find_by(name: 'Octatrack') + source_format = AudioFormat.new(file_type: 'mp3', sample_rate: 96_000, bit_depth: 32) + target_format = octatrack.target_format(source_format, 'song.mp3') + assert_equal 'wav', target_format.file_type + assert_equal 44_100, target_format.sample_rate + assert_equal 24, target_format.bit_depth + end + test '.target_bit_depth returns closest supported bit depth' do tp7 = Device.find_by(name: 'TP-7') assert_equal 16, tp7.target_bit_depth(8) diff --git a/test/wavesync/file_converter_test.rb b/test/wavesync/file_converter_test.rb index d1ad29c..8e7c439 100644 --- a/test/wavesync/file_converter_test.rb +++ b/test/wavesync/file_converter_test.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require_relative 'test_case' +require_relative '../../lib/wavesync/audio_format' require_relative '../../lib/wavesync/file_converter' require_relative '../../lib/wavesync/path_resolver' require_relative '../../lib/wavesync/device' @@ -27,7 +28,9 @@ def teardown FileUtils.touch(File.join(@source_dir, 'track.mp3')) audio = stub(bpm: nil) - result = @converter.convert(audio, source_aiff, @path_resolver, 'mp3', 44_100, nil, 16, nil) + source_format = AudioFormat.new(file_type: 'aiff', sample_rate: 44_100, bit_depth: 16) + target_format = AudioFormat.new(file_type: 'mp3', sample_rate: nil, bit_depth: nil) + result = @converter.convert(audio, source_aiff, @path_resolver, source_format, target_format) assert_equal false, result end @@ -39,7 +42,9 @@ def teardown audio = stub(bpm: nil) audio.stubs(:transcode) - result = @converter.convert(audio, source_aiff, @path_resolver, 'mp3', 44_100, nil, 16, nil) + source_format = AudioFormat.new(file_type: 'aiff', sample_rate: 44_100, bit_depth: 16) + target_format = AudioFormat.new(file_type: 'mp3', sample_rate: nil, bit_depth: nil) + result = @converter.convert(audio, source_aiff, @path_resolver, source_format, target_format) assert_equal true, result end @@ -50,7 +55,9 @@ def teardown FileUtils.touch(File.join(@target_dir, 'track.mp3')) audio = stub(bpm: nil) - result = @converter.convert(audio, source_aiff, @path_resolver, 'mp3', 44_100, nil, 16, nil) + source_format = AudioFormat.new(file_type: 'aiff', sample_rate: 44_100, bit_depth: 16) + target_format = AudioFormat.new(file_type: 'mp3', sample_rate: nil, bit_depth: nil) + result = @converter.convert(audio, source_aiff, @path_resolver, source_format, target_format) assert_equal false, result end