2121can_log = args .output is not None or debug_output
2222spectrogram_period = args .period
2323
24- input_path = args .input .expanduser (). resolve ()
24+ input_path = args .input .expanduser ()
2525
2626# Read WAV file
2727sample_rate , data = scipy .io .wavfile .read (input_path )
2828
2929# Convert to mono if necessary
3030if len (data .shape ) > 1 and data .shape [1 ] > 1 :
31+ print (f"Audio has { data .shape [1 ]} channels, using only the first channel." )
3132 data = data [:, 0 ]
3233
34+ sample_count = data .shape [0 ]
35+ track_length = sample_count / sample_rate
36+ if can_log :
37+ print (f"Audio has { sample_count } samples at { sample_rate } Hz, { track_length :.2f} seconds long." )
38+
39+
3340def constrain (value , min_value , max_value ):
3441 return min (max (value , min_value ), max_value )
3542
43+
3644def create_sound_instruction (start_freq : int , end_freq : int , start_vol : int ,
3745 end_vol : int , duration : int ) -> str :
3846 return struct .pack ("<BBHHHHH" ,
@@ -44,15 +52,18 @@ def create_sound_instruction(start_freq: int, end_freq: int, start_vol: int,
4452 max (end_freq , 1 )
4553 ).hex ()
4654
55+
4756def moving_average_1d (arr , window_size = 3 ):
4857 return np .convolve (arr , np .ones (window_size )/ window_size , mode = "same" )
4958
59+
5060def moving_average_2d (arr , window_size = 3 ):
5161 smoothed = np .zeros_like (arr )
5262 for i in range (arr .shape [0 ]):
5363 smoothed [i ] = moving_average_1d (arr [i ], window_size )
5464 return smoothed
5565
66+
5667def audio_to_makecode_arcade (data , sample_rate , period ) -> str :
5768 f , t , Sxx = scipy .signal .spectrogram (
5869 data ,
@@ -69,6 +80,7 @@ def audio_to_makecode_arcade(data, sample_rate, period) -> str:
6980 loudest_amplitudes = Sxx [loudest_indices , np .arange (Sxx .shape [1 ])].transpose ()
7081 max_amp = np .max (Sxx )
7182
83+ # Smooth amplitudes for cleaner sound
7284 loudest_amplitudes = moving_average_2d (loudest_amplitudes , window_size = 3 )
7385
7486 sound_instruction_buffers = ["" ] * len (frequency_buckets )
@@ -92,6 +104,7 @@ def audio_to_makecode_arcade(data, sample_rate, period) -> str:
92104 else :
93105 sound_instruction_buffers [bucket_index ] += create_sound_instruction (0 , 0 , 0 , 0 , period )
94106
107+ # Wrap each buffer in hex`` properly
95108 sound_instruction_buffers = [f"hex`{ buf } `" for buf in sound_instruction_buffers ]
96109
97110 # Only queuePlayInstructions function to avoid duplicates
@@ -109,10 +122,15 @@ def audio_to_makecode_arcade(data, sample_rate, period) -> str:
109122 )
110123 return code
111124
125+
112126code = audio_to_makecode_arcade (data , sample_rate , spectrogram_period )
113127
128+ # Always overwrite output
114129if args .output is not None :
115- output_path = args .output .expanduser ().resolve ()
130+ output_path = Path (args .output )
131+ output_path .parent .mkdir (parents = True , exist_ok = True )
116132 output_path .write_text (code )
133+ if can_log :
134+ print (f"Written output to { output_path } " )
117135else :
118136 print (code )
0 commit comments