rcrath / wvtbl

Wavetable processor
GNU General Public License v3.0
0 stars 0 forks source link

Reincorporate "first sample" tests and fix #41

Closed rcrath closed 2 months ago

rcrath commented 2 months ago

find in an old version the test for well formed first samples that combines half waves and deletes extras. e.g. seg 0 is /\ and seg 2 is \/. combine to be ful cycle of /\/

rcrath commented 2 months ago

Compare the way the last git script handles the first and last segments to the current approach. the currrent approach leaves stray half-cycles as the first one or two segtments. the git version does not do this. here is the git verion run followed by the current version run, followed by the function definitions for each.
git version:

segment_sizes = []  # Initialize the list to hold segment sizes
prev_start_index = 0  # Start from the beginning
some_small_amplitude = 10**(amplitude_tolerance_db / 20)  # Convert to linear scale
base_prep_192k32b_path = os.path.join(tmp_folder, base_prep_192k32b)
data, samplerate = sf.read(base_prep_192k32b_path)

# Process the first segment explicitly if the start is near zero
if abs(data[0]) < some_small_amplitude:  # Define 'some_small_amplitude' based on your fades
    for i in range(1, len(data)):
        if is_rising_zero_crossing(data, i):
            prev_start_index = i
            break  # Found the first real zero crossing, move to normal processing

# Variables to hold the first two segments temporarily
first_segment = None
second_segment = None
segment_index = 0

# Read the audio data and sampling rate from the file

for i in range(1, len(data)):
    if is_rising_zero_crossing(data, i):
        # Check if the current index is different from the previous start index
        if i != prev_start_index:
            wave_cycle = data[prev_start_index:i]
            prev_start_index = i
            # Store the size of this segment
            segment_sizes.append(len(wave_cycle))
            # Only proceed if wave_cycle is not empty
            if len(wave_cycle) > 0:
                # Debug: Print the segment's properties
                if segment_index == 0 or segment_index == 1:
                    # Print the length of the segment
                    #  print(f"Debug: Segment {segment_index} length: {len(wave_cycle)} samples")
                    # Print the amplitude at the beginning of the segment in dB
                    #  print(f"Debug: Segment {segment_index} start amplitude: -{-20 * np.log10(max(abs(wave_cycle[0]), 1e-10))} dB")
                    # Print the amplitude at the end of the segment in dB
                    #  print(f"Debug: Segment {segment_index} end amplitude: -{-20 * np.log10(max(abs(wave_cycle[-1]), 1e-10))} dB")

                    # Check if this is the first or second segment
                    if segment_index == 0:
                        first_segment = wave_cycle
                    elif segment_index == 1:
                        second_segment = wave_cycle

                # Write segment to file if it's not the first or second segment
                if segment_index > 1 or segment_index == 0:
                    base_seg = f"{base}_seg_{segment_index:04d}{ext}"
                    tmp_base_seg_path = os.path.join(tmp_folder, base_seg)
                    wavfile.write(tmp_base_seg_path, samplerate, wave_cycle)
                    #  print(f"Debug: Segment {segment_index} written: {tmp_base_seg_path}")

                segment_index += 1
            # else:
                # print(f"Warning: Empty wave cycle detected at index {i}")

# Check if the first two segments contain full wave cycles
if first_segment is not None and second_segment is not None:
    full_cycle_first = is_full_wavecycle(first_segment)
    full_cycle_second = is_full_wavecycle(second_segment)

    # Debug: Print the evaluation of the first two segments
    #  print(f"Debug: First segment is a full cycle: {full_cycle_first}")
    #  print(f"Debug: Second segment is a full cycle: {full_cycle_second}")

    if not full_cycle_first or not full_cycle_second:
        # Combine the first two segments
        combined_segment = np.concatenate((first_segment, second_segment))

        # Write the combined segment to the '0001' file
        combined_path = os.path.join(tmp_folder, f"{base}_seg_0001{ext}")
        wavfile.write(combined_path, samplerate, combined_segment)
        #  print(f"Debug: Combined segment written: {combined_path}")

        # Delete the '0000' file if it exists
        first_path = os.path.join(tmp_folder, f"{base}_seg_0000{ext}")
        if os.path.exists(first_path):
            os.remove(first_path)
            #  print(f"Debug: Deleted: {first_path}")
    else:
        # If both segments are full cycles, write them out as normal
        for i, segment in enumerate([first_segment, second_segment], start=0):
            segment_path = os.path.join(tmp_folder, f"{base}_seg_{i:04d}{ext}")
            wavfile.write(segment_path, samplerate, segment)
            #  print(f"Debug: Segment {i} written: {segment_path}")

# Handle the last segment
if prev_start_index < len(data):
    wave_cycle = data[prev_start_index:]
    # Check if the wave cycle is full before writing to file
    if is_full_wavecycle(wave_cycle):
        wavfile.write(tmp_base_seg_path, samplerate, wave_cycle)
        # print(f"Debug: Final segment {segment_index} written: {tmp_base_seg_path}")
        segment_index += 1
    # else:
        # print(f"Debug: Final segment {segment_index} is not a full wave cycle and was not written.")

the current version:

NONE

and its functions: (from last git)

# Function to calculate rising zero crossings in a waveform
def is_rising_zero_crossing(data, index):
    # Ensure index is within the valid range
    if index <= 0 or index >= len(data) - 1:  # -1 to handle the end of the file
        return False

    # Check for a rising zero crossing: previous sample is negative, and the current one is positive
    if data[index - 1] < 0 and data[index] >= 0:
        return True

    return False

from aa_common (current)

def is_rising_zero_crossing(data, index):
    # Check if the data is stereo (2D array) or mono (1D array)
    if data.ndim == 2:  # Stereo case
        # Check zero-crossing for the left channel (index 0)
        if data[index - 1, 0] < 0 and data[index, 0] >= 0:
            return True
    else:  # Mono case
        if data[index - 1] < 0 and data[index] >= 0:
            return True
    return False

from git:

def is_full_wavecycle(segment):
    global amplitude_tolerance_db  # Use the global variable

    if len(segment) < 3:
        return False

    # Convert the first and last samples to dB
    first_sample_db = 20 * np.log10(max(abs(segment[0]), 1e-10))
    last_sample_db = 20 * np.log10(max(abs(segment[-1]), 1e-10))

    # Check if the first and last samples are near zero in dB
    if first_sample_db > amplitude_tolerance_db or last_sample_db > amplitude_tolerance_db:
        return False

    # Detect zero crossings
    zero_crossings = np.where(np.diff(np.signbit(segment)))[0]

    # Ensure there's at least one significant zero crossing
    if len(zero_crossings) < 2:
        return False

    return True

from aa_common:

def is_full_wavecycle(segment, amplitude_tolerance_db=-60):
    if len(segment) < 3:
        return False

    first_sample_db = 20 * np.log10(max(abs(segment[0]), 1e-10))
    last_sample_db = 20 * np.log10(max(abs(segment[-1]), 1e-10))

    if first_sample_db > amplitude_tolerance_db or last_sample_db > amplitude_tolerance_db:
        return False

    zero_crossings = np.where(np.diff(np.signbit(segment)))[0]

    if len(zero_crossings) < 2:
        return False

    return True
rcrath commented 2 months ago

solved it simpler than originally.