mirror of
https://github.com/Yonokid/PyTaiko.git
synced 2026-02-04 11:40:13 +01:00
hotfixes
This commit is contained in:
@@ -220,15 +220,13 @@ class Music:
|
|||||||
self.preview = preview # Preview start time in seconds
|
self.preview = preview # Preview start time in seconds
|
||||||
self.is_preview_mode = preview is not None
|
self.is_preview_mode = preview is not None
|
||||||
|
|
||||||
# Add preview-specific attributes
|
|
||||||
self.preview_start_frame = 0
|
self.preview_start_frame = 0
|
||||||
self.preview_end_frame = 0
|
self.preview_end_frame = 0
|
||||||
self.uses_file_streaming = False # Flag to indicate streaming vs memory loading
|
self.uses_file_streaming = False
|
||||||
|
|
||||||
self.file_buffer_size = int(target_sample_rate * 5) # 5 seconds buffer
|
self.file_buffer_size = int(target_sample_rate * 5) # 5 seconds buffer
|
||||||
self.buffer = None
|
self.buffer = None
|
||||||
self.buffer_position = 0
|
self.buffer_position = 0
|
||||||
self.buffer_original_frames = 0 # Track original frames for resampling
|
|
||||||
|
|
||||||
# Thread-safe updates
|
# Thread-safe updates
|
||||||
self.lock = Lock()
|
self.lock = Lock()
|
||||||
@@ -245,7 +243,6 @@ class Music:
|
|||||||
if self.data is None:
|
if self.data is None:
|
||||||
raise Exception("No data provided for memory loading")
|
raise Exception("No data provided for memory loading")
|
||||||
|
|
||||||
# Convert to float32 if needed
|
|
||||||
if self.data.dtype != float32:
|
if self.data.dtype != float32:
|
||||||
self.data = self.data.astype(float32)
|
self.data = self.data.astype(float32)
|
||||||
|
|
||||||
@@ -260,11 +257,9 @@ class Music:
|
|||||||
rms_scale_factor = target_rms / current_rms
|
rms_scale_factor = target_rms / current_rms
|
||||||
self.data *= rms_scale_factor
|
self.data *= rms_scale_factor
|
||||||
|
|
||||||
# Determine channels and total frames
|
|
||||||
if self.data.ndim == 1:
|
if self.data.ndim == 1:
|
||||||
self.channels = 1
|
self.channels = 1
|
||||||
self.total_frames = len(self.data)
|
self.total_frames = len(self.data)
|
||||||
# Reshape for consistency
|
|
||||||
self.data = self.data.reshape(-1, 1)
|
self.data = self.data.reshape(-1, 1)
|
||||||
else:
|
else:
|
||||||
self.channels = self.data.shape[1]
|
self.channels = self.data.shape[1]
|
||||||
@@ -292,41 +287,27 @@ class Music:
|
|||||||
original_total_frames = self.sound_file.frames
|
original_total_frames = self.sound_file.frames
|
||||||
|
|
||||||
if self.is_preview_mode:
|
if self.is_preview_mode:
|
||||||
# Calculate preview boundaries but don't load data into memory
|
|
||||||
self.preview_start_frame = int(self.preview * self.sample_rate)
|
self.preview_start_frame = int(self.preview * self.sample_rate)
|
||||||
|
|
||||||
# For preview, we'll calculate the available frames from the start position
|
|
||||||
available_frames = original_total_frames - self.preview_start_frame
|
available_frames = original_total_frames - self.preview_start_frame
|
||||||
self.preview_end_frame = min(self.preview_start_frame + available_frames, original_total_frames)
|
self.preview_end_frame = min(self.preview_start_frame + available_frames, original_total_frames)
|
||||||
|
|
||||||
# Ensure preview start is within bounds
|
|
||||||
if self.preview_start_frame >= original_total_frames:
|
if self.preview_start_frame >= original_total_frames:
|
||||||
self.preview_start_frame = max(0, original_total_frames - available_frames)
|
self.preview_start_frame = max(0, original_total_frames - available_frames)
|
||||||
self.preview_end_frame = original_total_frames
|
self.preview_end_frame = original_total_frames
|
||||||
|
|
||||||
# Set total frames to the preview length
|
|
||||||
self.total_frames = self.preview_end_frame - self.preview_start_frame
|
self.total_frames = self.preview_end_frame - self.preview_start_frame
|
||||||
|
|
||||||
# Seek to preview start position
|
|
||||||
self.sound_file.seek(self.preview_start_frame)
|
self.sound_file.seek(self.preview_start_frame)
|
||||||
|
|
||||||
# Enable file streaming mode (don't load into memory)
|
|
||||||
self.uses_file_streaming = True
|
self.uses_file_streaming = True
|
||||||
self.data = None # Don't store full data in memory
|
self.data = None # Don't store full data in memory
|
||||||
|
|
||||||
print(f"Preview mode: Streaming {self.total_frames} frames ({self.total_frames/self.sample_rate:.2f}s) starting at {self.preview:.2f}s")
|
print(f"Preview mode: Streaming {self.total_frames} frames ({self.total_frames/self.sample_rate:.2f}s) starting at {self.preview:.2f}s")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# For non-preview mode, load entire file into memory as before
|
|
||||||
self.data = self.sound_file.read()
|
self.data = self.sound_file.read()
|
||||||
self.total_frames = original_total_frames
|
self.total_frames = original_total_frames
|
||||||
self.uses_file_streaming = False
|
self.uses_file_streaming = False
|
||||||
|
|
||||||
# Process the loaded data
|
|
||||||
self.load_from_memory()
|
self.load_from_memory()
|
||||||
return # Early return to avoid duplicate processing
|
return # Early return to avoid duplicate processing
|
||||||
|
|
||||||
# Initialize buffer for streaming mode
|
|
||||||
self._fill_buffer()
|
self._fill_buffer()
|
||||||
self.valid = True
|
self.valid = True
|
||||||
|
|
||||||
@@ -358,19 +339,17 @@ class Music:
|
|||||||
if self.data is None:
|
if self.data is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
start_frame = self.position
|
start_frame = self.position + self.buffer_position
|
||||||
end_frame = min(start_frame + self.file_buffer_size, self.total_frames)
|
end_frame = min(start_frame + self.file_buffer_size, self.total_frames)
|
||||||
|
|
||||||
if start_frame >= self.total_frames:
|
if start_frame >= self.total_frames:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Extract the chunk of data
|
|
||||||
data_chunk = self.data[start_frame:end_frame]
|
data_chunk = self.data[start_frame:end_frame]
|
||||||
|
|
||||||
self.buffer = data_chunk
|
self.buffer = data_chunk
|
||||||
|
self.position += self.buffer_position
|
||||||
self.buffer_position = 0
|
self.buffer_position = 0
|
||||||
# For memory mode, original frames equals buffer frames since no resampling happens here
|
|
||||||
self.buffer_original_frames = len(data_chunk) if data_chunk.ndim == 1 else data_chunk.shape[0]
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _fill_buffer_from_file(self) -> bool:
|
def _fill_buffer_from_file(self) -> bool:
|
||||||
@@ -378,37 +357,26 @@ class Music:
|
|||||||
if not self.sound_file:
|
if not self.sound_file:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Calculate current absolute position in file (in original sample rate)
|
|
||||||
current_absolute_position = self.preview_start_frame + self.position
|
current_absolute_position = self.preview_start_frame + self.position
|
||||||
|
|
||||||
# Check if we've reached the end of the preview
|
|
||||||
if current_absolute_position >= self.preview_end_frame:
|
if current_absolute_position >= self.preview_end_frame:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Calculate how many frames to read (respecting preview boundaries)
|
|
||||||
# This is in the original sample rate
|
|
||||||
frames_to_read = min(self.file_buffer_size, self.preview_end_frame - current_absolute_position)
|
frames_to_read = min(self.file_buffer_size, self.preview_end_frame - current_absolute_position)
|
||||||
|
|
||||||
if frames_to_read <= 0:
|
if frames_to_read <= 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Seek to the correct position
|
|
||||||
self.sound_file.seek(current_absolute_position)
|
self.sound_file.seek(current_absolute_position)
|
||||||
|
|
||||||
# Read the data chunk
|
|
||||||
data_chunk = self.sound_file.read(frames_to_read)
|
data_chunk = self.sound_file.read(frames_to_read)
|
||||||
|
|
||||||
if len(data_chunk) == 0:
|
if len(data_chunk) == 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Store the original length before any processing
|
|
||||||
original_frames = len(data_chunk) if data_chunk.ndim == 1 else data_chunk.shape[0]
|
original_frames = len(data_chunk) if data_chunk.ndim == 1 else data_chunk.shape[0]
|
||||||
|
|
||||||
# Apply resampling if needed
|
|
||||||
if self.sample_rate != self.target_sample_rate:
|
if self.sample_rate != self.target_sample_rate:
|
||||||
data_chunk = resample(data_chunk, self.sample_rate, self.target_sample_rate)
|
data_chunk = resample(data_chunk, self.sample_rate, self.target_sample_rate)
|
||||||
|
|
||||||
# Apply normalization if needed
|
|
||||||
if self.normalize is not None:
|
if self.normalize is not None:
|
||||||
current_rms = get_average_volume_rms(data_chunk)
|
current_rms = get_average_volume_rms(data_chunk)
|
||||||
if current_rms > 0:
|
if current_rms > 0:
|
||||||
@@ -416,19 +384,14 @@ class Music:
|
|||||||
rms_scale_factor = target_rms / current_rms
|
rms_scale_factor = target_rms / current_rms
|
||||||
data_chunk *= rms_scale_factor
|
data_chunk *= rms_scale_factor
|
||||||
|
|
||||||
# Ensure proper shape
|
|
||||||
if data_chunk.ndim == 1 and self.channels > 1:
|
if data_chunk.ndim == 1 and self.channels > 1:
|
||||||
data_chunk = data_chunk.reshape(-1, self.channels)
|
data_chunk = data_chunk.reshape(-1, self.channels)
|
||||||
elif data_chunk.ndim == 2 and self.channels == 1:
|
elif data_chunk.ndim == 2 and self.channels == 1:
|
||||||
data_chunk = data_chunk.flatten().reshape(-1, 1)
|
data_chunk = data_chunk.flatten().reshape(-1, 1)
|
||||||
|
|
||||||
self.buffer = data_chunk
|
self.buffer = data_chunk
|
||||||
|
self.position += original_frames
|
||||||
self.buffer_position = 0
|
self.buffer_position = 0
|
||||||
|
|
||||||
# Store how many original frames this buffer represents
|
|
||||||
# This is crucial for position tracking when resampling occurs
|
|
||||||
self.buffer_original_frames = original_frames
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def update(self) -> None:
|
def update(self) -> None:
|
||||||
@@ -437,24 +400,14 @@ class Music:
|
|||||||
return
|
return
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
# Check if we need to refill the buffer
|
|
||||||
if self.buffer is None:
|
if self.buffer is None:
|
||||||
return
|
return
|
||||||
if self.buffer_position >= len(self.buffer):
|
if self.buffer_position >= len(self.buffer):
|
||||||
# Update position based on original frames consumed
|
|
||||||
if self.uses_file_streaming:
|
|
||||||
# For file streaming, we need to track position in original sample rate
|
|
||||||
self.position += self.buffer_original_frames
|
|
||||||
else:
|
|
||||||
# For memory mode, position is already in target sample rate
|
|
||||||
self.position += self.buffer_position
|
|
||||||
self.buffer_position = 0
|
|
||||||
self.is_playing = self._fill_buffer()
|
self.is_playing = self._fill_buffer()
|
||||||
|
|
||||||
def play(self) -> None:
|
def play(self) -> None:
|
||||||
"""Start playing the music stream"""
|
"""Start playing the music stream"""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
# Reset position if at the end
|
|
||||||
if self.position >= self.total_frames:
|
if self.position >= self.total_frames:
|
||||||
self.position = 0
|
self.position = 0
|
||||||
self.buffer_position = 0
|
self.buffer_position = 0
|
||||||
@@ -493,16 +446,12 @@ class Music:
|
|||||||
def seek(self, position_seconds) -> None:
|
def seek(self, position_seconds) -> None:
|
||||||
"""Seek to a specific position in seconds (relative to preview start if in preview mode)"""
|
"""Seek to a specific position in seconds (relative to preview start if in preview mode)"""
|
||||||
with self.lock:
|
with self.lock:
|
||||||
# Convert seconds to frames
|
|
||||||
frame_position = int(position_seconds * self.target_sample_rate)
|
frame_position = int(position_seconds * self.target_sample_rate)
|
||||||
|
|
||||||
# Clamp position to valid range
|
|
||||||
frame_position = max(0, min(frame_position, self.total_frames - 1))
|
frame_position = max(0, min(frame_position, self.total_frames - 1))
|
||||||
|
|
||||||
self.position = frame_position
|
self.position = frame_position
|
||||||
self.buffer_position = 0
|
self.buffer_position = 0
|
||||||
|
|
||||||
# For file streaming, we'll let _fill_buffer handle the seeking
|
|
||||||
self._fill_buffer()
|
self._fill_buffer()
|
||||||
|
|
||||||
def get_time_length(self) -> float:
|
def get_time_length(self) -> float:
|
||||||
@@ -533,18 +482,7 @@ class Music:
|
|||||||
if self.buffer is None:
|
if self.buffer is None:
|
||||||
return zeros(num_frames, dtype=float32)
|
return zeros(num_frames, dtype=float32)
|
||||||
|
|
||||||
# Check if we need more data
|
|
||||||
if self.buffer_position >= len(self.buffer):
|
if self.buffer_position >= len(self.buffer):
|
||||||
# Update position based on what we actually consumed from the original file
|
|
||||||
if self.uses_file_streaming:
|
|
||||||
# For file streaming, advance by original frames (before resampling)
|
|
||||||
self.position += self.buffer_original_frames
|
|
||||||
else:
|
|
||||||
# For memory mode, advance by resampled frames
|
|
||||||
self.position += self.buffer_position
|
|
||||||
self.buffer_position = 0
|
|
||||||
|
|
||||||
# Try to fill buffer again
|
|
||||||
if not self._fill_buffer():
|
if not self._fill_buffer():
|
||||||
self.is_playing = False
|
self.is_playing = False
|
||||||
if self.channels == 1:
|
if self.channels == 1:
|
||||||
@@ -552,7 +490,6 @@ class Music:
|
|||||||
else:
|
else:
|
||||||
return zeros((num_frames, self.channels), dtype=float32)
|
return zeros((num_frames, self.channels), dtype=float32)
|
||||||
|
|
||||||
# Calculate how many frames we have left in buffer
|
|
||||||
frames_left_in_buffer = len(self.buffer) - self.buffer_position
|
frames_left_in_buffer = len(self.buffer) - self.buffer_position
|
||||||
if self.channels > 1:
|
if self.channels > 1:
|
||||||
frames_left_in_buffer = self.buffer.shape[0] - self.buffer_position
|
frames_left_in_buffer = self.buffer.shape[0] - self.buffer_position
|
||||||
@@ -566,13 +503,10 @@ class Music:
|
|||||||
output = zeros((num_frames, self.channels), dtype=float32)
|
output = zeros((num_frames, self.channels), dtype=float32)
|
||||||
output[:frames_to_get] = self.buffer[self.buffer_position:self.buffer_position+frames_to_get]
|
output[:frames_to_get] = self.buffer[self.buffer_position:self.buffer_position+frames_to_get]
|
||||||
|
|
||||||
# Update buffer position
|
|
||||||
self.buffer_position += frames_to_get
|
self.buffer_position += frames_to_get
|
||||||
|
|
||||||
# Apply volume
|
|
||||||
output *= self.volume
|
output *= self.volume
|
||||||
|
|
||||||
# Apply pan for stereo output
|
|
||||||
if self.channels == 2 and self.pan != 0.5:
|
if self.channels == 2 and self.pan != 0.5:
|
||||||
# pan=0: full left, pan=0.5: center, pan=1: full right
|
# pan=0: full left, pan=0.5: center, pan=1: full right
|
||||||
left_vol = min(1.0, 2.0 * (1.0 - self.pan))
|
left_vol = min(1.0, 2.0 * (1.0 - self.pan))
|
||||||
|
|||||||
Reference in New Issue
Block a user