@@ -35,8 +35,13 @@ class SoundRecorder(Activity):
3535 """
3636
3737 # Constants
38- MAX_DURATION_MS = 60000 # 60 seconds max recording
3938 RECORDINGS_DIR = "data/recordings"
39+ SAMPLE_RATE = 16000 # 16kHz
40+ BYTES_PER_SAMPLE = 2 # 16-bit audio
41+ BYTES_PER_SECOND = SAMPLE_RATE * BYTES_PER_SAMPLE # 32000 bytes/sec
42+ MIN_DURATION_MS = 5000 # Minimum 5 seconds
43+ MAX_DURATION_MS = 3600000 # Maximum 1 hour (absolute cap)
44+ SAFETY_MARGIN = 0.80 # Use only 80% of available space
4045
4146 # UI Widgets
4247 _status_label = None
@@ -57,6 +62,9 @@ class SoundRecorder(Activity):
5762 def onCreate (self ):
5863 screen = lv .obj ()
5964
65+ # Calculate max duration based on available storage
66+ self ._current_max_duration_ms = self ._calculate_max_duration ()
67+
6068 # Title
6169 title = lv .label (screen )
6270 title .set_text ("Sound Recorder" )
@@ -69,7 +77,7 @@ def onCreate(self):
6977
7078 # Timer display
7179 self ._timer_label = lv .label (screen )
72- self ._timer_label .set_text ("00:00 / 01:00" )
80+ self ._timer_label .set_text (self . _format_timer_text ( 0 ) )
7381 self ._timer_label .align (lv .ALIGN .CENTER , 0 , - 30 )
7482 self ._timer_label .set_style_text_font (lv .font_montserrat_24 , 0 )
7583
@@ -123,6 +131,9 @@ def onCreate(self):
123131
124132 def onResume (self , screen ):
125133 super ().onResume (screen )
134+ # Recalculate max duration (storage may have changed)
135+ self ._current_max_duration_ms = self ._calculate_max_duration ()
136+ self ._timer_label .set_text (self ._format_timer_text (0 ))
126137 self ._update_status ()
127138 self ._find_last_recording ()
128139
@@ -170,6 +181,57 @@ def _find_last_recording(self):
170181 print (f"SoundRecorder: Error finding recordings: { e } " )
171182 self ._last_recording = None
172183
184+ def _calculate_max_duration (self ):
185+ """
186+ Calculate maximum recording duration based on available storage.
187+ Returns duration in milliseconds.
188+ """
189+ try :
190+ # Ensure recordings directory exists
191+ _makedirs (self .RECORDINGS_DIR )
192+
193+ # Get filesystem stats for the recordings directory
194+ stat = os .statvfs (self .RECORDINGS_DIR )
195+
196+ # Calculate free space in bytes
197+ # f_bavail = free blocks available to non-superuser
198+ # f_frsize = fragment size (fundamental block size)
199+ free_bytes = stat [0 ] * stat [4 ] # f_frsize * f_bavail
200+
201+ # Apply safety margin (use only 80% of available space)
202+ usable_bytes = int (free_bytes * self .SAFETY_MARGIN )
203+
204+ # Calculate max duration in seconds
205+ max_seconds = usable_bytes // self .BYTES_PER_SECOND
206+
207+ # Convert to milliseconds
208+ max_ms = max_seconds * 1000
209+
210+ # Clamp to min/max bounds
211+ max_ms = max (self .MIN_DURATION_MS , min (max_ms , self .MAX_DURATION_MS ))
212+
213+ print (f"SoundRecorder: Free space: { free_bytes } bytes, "
214+ f"usable: { usable_bytes } bytes, max duration: { max_ms // 1000 } s" )
215+
216+ return max_ms
217+
218+ except Exception as e :
219+ print (f"SoundRecorder: Error calculating max duration: { e } " )
220+ # Fall back to a conservative 60 seconds
221+ return 60000
222+
223+ def _format_timer_text (self , elapsed_ms ):
224+ """Format timer display text showing elapsed / max time."""
225+ elapsed_sec = elapsed_ms // 1000
226+ max_sec = self ._current_max_duration_ms // 1000
227+
228+ elapsed_min = elapsed_sec // 60
229+ elapsed_sec_display = elapsed_sec % 60
230+ max_min = max_sec // 60
231+ max_sec_display = max_sec % 60
232+
233+ return f"{ elapsed_min :02d} :{ elapsed_sec_display :02d} / { max_min :02d} :{ max_sec_display :02d} "
234+
173235 def _generate_filename (self ):
174236 """Generate a timestamped filename for the recording."""
175237 # Get current time
@@ -200,17 +262,26 @@ def _start_recording(self):
200262 file_path = self ._generate_filename ()
201263 print (f"SoundRecorder: Generated filename: { file_path } " )
202264
265+ # Recalculate max duration before starting (storage may have changed)
266+ self ._current_max_duration_ms = self ._calculate_max_duration ()
267+
268+ if self ._current_max_duration_ms < self .MIN_DURATION_MS :
269+ print ("SoundRecorder: Not enough storage space" )
270+ self ._status_label .set_text ("Not enough storage space" )
271+ self ._status_label .set_style_text_color (lv .color_hex (0xAA0000 ), 0 )
272+ return
273+
203274 # Start recording
204275 print (f"SoundRecorder: Calling AudioFlinger.record_wav()" )
205276 print (f" file_path: { file_path } " )
206- print (f" duration_ms: { self .MAX_DURATION_MS } " )
207- print (f" sample_rate: 16000 " )
277+ print (f" duration_ms: { self ._current_max_duration_ms } " )
278+ print (f" sample_rate: { self . SAMPLE_RATE } " )
208279
209280 success = AudioFlinger .record_wav (
210281 file_path = file_path ,
211- duration_ms = self .MAX_DURATION_MS ,
282+ duration_ms = self ._current_max_duration_ms ,
212283 on_complete = self ._on_recording_complete ,
213- sample_rate = 16000
284+ sample_rate = self . SAMPLE_RATE
214285 )
215286
216287 print (f"SoundRecorder: record_wav returned: { success } " )
@@ -281,25 +352,15 @@ def _stop_timer_update(self):
281352 if self ._timer_task :
282353 self ._timer_task .delete ()
283354 self ._timer_task = None
284- self ._timer_label .set_text ("00:00 / 01:00" )
355+ self ._timer_label .set_text (self . _format_timer_text ( 0 ) )
285356
286357 def _update_timer (self , timer ):
287358 """Update timer display (called periodically)."""
288359 if not self ._is_recording :
289360 return
290361
291362 elapsed_ms = time .ticks_diff (time .ticks_ms (), self ._record_start_time )
292- elapsed_sec = elapsed_ms // 1000
293- max_sec = self .MAX_DURATION_MS // 1000
294-
295- elapsed_min = elapsed_sec // 60
296- elapsed_sec = elapsed_sec % 60
297- max_min = max_sec // 60
298- max_sec_display = max_sec % 60
299-
300- self ._timer_label .set_text (
301- f"{ elapsed_min :02d} :{ elapsed_sec :02d} / { max_min :02d} :{ max_sec_display :02d} "
302- )
363+ self ._timer_label .set_text (self ._format_timer_text (elapsed_ms ))
303364
304365 def _on_play_clicked (self , event ):
305366 """Handle play button click."""
0 commit comments