├── .github └── workflows │ └── python-package.yml ├── LICENSE ├── README.md ├── requirements.txt ├── soundstorm-logo.png └── soundstorm.py /.github/workflows/python-package.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python 3 | 4 | name: Python package 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | python-version: ["3.9", "3.10", "3.11"] 20 | 21 | steps: 22 | - uses: actions/checkout@v3 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v3 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | - name: Install dependencies 28 | run: | 29 | python -m pip install --upgrade pip 30 | python -m pip install flake8 pytest 31 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 32 | - name: Lint with flake8 33 | run: | 34 | # stop the build if there are Python syntax errors or undefined names 35 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics 36 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 37 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics 38 | - name: Test with pytest 39 | run: | 40 | pytest 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Daniel Sheils 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files ("soundstorm"), to deal 7 | in this software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of this software, and to permit persons to whom this software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | SOUNDSTORM IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Soundstorm Logo](soundstorm-logo.png) 2 | 3 | # Soundstorm: A Comprehensive Audio & AI Experience 4 | 5 | ## Overview 6 | 7 | Soundstorm is a cutting-edge AI-powered audio manipulation application designed to provide a rich yet simplified experience for sound designers, algorithmic composers, and experimental audio enthusiasts. From sample pack creation and algorithmic composition to AI text-to-audio and onscreen ChatGPT, Soundstorm is a sonic powerhouse. 8 | 9 | ### Features 10 | 11 | - 🎵 **AI Text-to-Audio Generation**: Generate any song or sound you want by a simple text prompt. 12 | - 📦 **Algorithmic Sample Pack Creation**: Automatically generate sample packs based on user-defined criteria. 13 | - 🎛 **Audio Effects**: Real-time audio effects like reverb, distortion, and more. 14 | - 🎶 **Algorithmic Composition**: Create algorithmic composition. 15 | - 🎹 **MIDI Randomizer**: Generate random MIDI sequences for creative inspiration. 16 | - 🗨️ **Chat with GPT**: Integrated chat using OpenAI's GPT models for real-time conversational experiences. 17 | - 🎲 **GPT Randomizer**: Generate random text snippets, prompts, or even song lyrics. 18 | 19 | ## Installation 20 | 21 | ### Prerequisites 22 | 23 | - Python 3.11 24 | 25 | ### Install Dependencies 26 | 27 | Clone the repository and navigate into the project directory. Run the following command to install all the necessary packages: 28 | 29 | ```bash 30 | pip install pedalboard pydub replicate midiutil soundfile openai numpy pygame 31 | ``` 32 | 33 | ### API Keys 34 | 35 | You'll need to have API keys for both Replicate and OpenAI: 36 | 37 | - For Replicate, sign up at: https://replicate.com and follow the instructions to get your API key. 38 | - For OpenAI, sign up at: https://openai.com/product and follow the instructions to get your API key. 39 | 40 | Add these keys in the appropriate sections within the application before running it. 41 | 42 | ### Running the Application 43 | 44 | After installing the prerequisites and obtaining the API keys, you can run the application using: 45 | 46 | ```bash 47 | python Soundstorm.py 48 | ``` 49 | 50 | ### Important 51 | 52 | This is SUPER rough. Currently, it only exists as a Python script and has only been tested on 2 different Macs. It is more or less a working prototype with a super stripped-down GUI. It can be buggy and does require finagling. Also things definitely need to be cleaned up, labeled better, etc etc. I could use assistance with this! 53 | 54 | ## Contributing 55 | 56 | Contributions are welcome! This is the work of an amateur. Would love to see it fleshed out! 57 | 58 | ## Support 59 | 60 | For support and feature requests, please open an issue on this repository. 61 | 62 | [![Support via PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.me/noodlebake) 63 | 64 | ## License 65 | 66 | This project is licensed under the MIT License. 67 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pydub 2 | tk 3 | pedalboard 4 | pydub 5 | replicate 6 | midiutil 7 | soundfile 8 | openai 9 | numpy 10 | pygame 11 | -------------------------------------------------------------------------------- /soundstorm-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RhythrosaLabs/soundstorm/32cb8bfa9abe318a3e79dc24a57bd191565b1a5e/soundstorm-logo.png -------------------------------------------------------------------------------- /soundstorm.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from pydub.generators import Sine, Square, Sawtooth, Triangle, Pulse, WhiteNoise 3 | from tkinter import simpledialog, messagebox 4 | from tkinter import ttk 5 | from tkinter import Button, Label, StringVar, filedialog 6 | import pygame 7 | import json 8 | import os 9 | import replicate 10 | import requests 11 | import tkinter as tk 12 | import openai 13 | import threading 14 | from midiutil import MIDIFile 15 | import random 16 | from pydub import AudioSegment 17 | from pydub.playback import play 18 | from pedalboard import Pedalboard, Chorus, Reverb 19 | from pedalboard.io import AudioFile 20 | from pedalboard import Pedalboard, Compressor, Distortion, Reverb 21 | 22 | 23 | #========================================================================================================= 24 | #========================================================================================================= 25 | 26 | # g l o b a l 27 | 28 | #========================================================================================================= 29 | #========================================================================================================= 30 | 31 | # a p i 32 | 33 | def save_api_keys(replicate_key, openai_key): 34 | with open("api_keys.json", "w") as f: 35 | json.dump({"replicate_api_key": replicate_key, "openai_api_key": openai_key}, f) 36 | 37 | def load_api_keys(): 38 | try: 39 | with open("api_keys.json", "r") as f: 40 | return json.load(f) 41 | except FileNotFoundError: 42 | return None 43 | 44 | def get_api_keys(): 45 | replicate_key = simpledialog.askstring("Input", "Enter your Replicate API Key:") 46 | openai_key = simpledialog.askstring("Input", "Enter your OpenAI API Key:") 47 | return replicate_key, openai_key 48 | 49 | 50 | #========================================================================================================= 51 | #========================================================================================================= 52 | 53 | # G P T R A N D O M I Z E 54 | 55 | def threaded_generate_random_prompt(): 56 | openai.api_key = load_api_keys().get('openai_api_key', '') 57 | messages = [ 58 | {"role": "system", "content": "You are a helpful assistant."}, 59 | {"role": "user", "content": "Pick between 1-3 percussive instruments, 1-3 world instruments, 1-3 other instruments, 1-3 niche genres, a tempo, and a song key, and list them in one sentence. No non-numerical characters except for commas"} 60 | ] 61 | try: 62 | res = openai.ChatCompletion.create(model="gpt-4", messages=messages, max_tokens=100, temperature=1) 63 | generated_text = res['choices'][0]['message']['content'].strip() 64 | text_input.delete("1.0", "end") 65 | text_input.insert("1.0", generated_text) 66 | except Exception as e: 67 | print(f"Error: {e}") 68 | 69 | def generate_random_prompt(): 70 | threading.Thread(target=threaded_generate_random_prompt).start() 71 | 72 | text_input = tk.Text() 73 | #========================================================================================================= 74 | #========================================================================================================= 75 | 76 | # C H A T G P T 77 | 78 | def get_gpt_response(user_message): 79 | openai.api_key = load_api_keys().get('openai_api_key', '') 80 | if not openai.api_key: 81 | return "Error: OpenAI API key not found." 82 | 83 | messages = [ 84 | {"role": "system", "content": "You are a helpful assistant."}, 85 | {"role": "user", "content": user_message} 86 | ] 87 | 88 | try: 89 | res = openai.ChatCompletion.create(model="gpt-4", messages=messages, max_tokens=100, temperature=1) 90 | return res['choices'][0]['message']['content'] 91 | except Exception as e: 92 | return f"Error: {e}" 93 | 94 | 95 | def send_message(): 96 | user_message = user_input.get() 97 | chat_display.config(state=tk.NORMAL) 98 | chat_display.insert(tk.END, "You: " + user_message + '\n') 99 | 100 | def threaded_gpt_response(): 101 | gpt_response = get_gpt_response(user_message) 102 | chat_display.insert(tk.END, "GPT-4: " + gpt_response + '\n') 103 | chat_display.config(state=tk.DISABLED) 104 | 105 | threading.Thread(target=threaded_gpt_response).start() 106 | user_input.delete(0, tk.END) 107 | 108 | 109 | # Example Tkinter widget initialization 110 | user_input = tk.Entry() 111 | chat_display = tk.Text() 112 | 113 | 114 | #========================================================================================================= 115 | #========================================================================================================= 116 | 117 | # P L A Y B A C K A N D T R A N S P O R T 118 | 119 | class MusicPlayer: 120 | def __init__(self, window): 121 | Load = Button(window, text='Load', width=3, font=('Times', 12), command=self.load) 122 | Play = Button(window, text='Play', width=3, font=('Times', 12), command=self.play) 123 | Pause = Button(window, text='Pause', width=3, font=('Times', 12), command=self.pause) 124 | Stop = Button(window, text='Stop', width=3, font=('Times', 12), command=self.stop) 125 | 126 | Load.grid(row=0, column=0) 127 | Play.grid(row=0, column=1) 128 | Pause.grid(row=0, column=2) 129 | Stop.grid(row=0, column=3) 130 | 131 | self.music_file = False 132 | self.playing_state = False 133 | 134 | self.track_time = StringVar() 135 | self.track_time.set('00:00') 136 | self.time_display = Label(window, textvariable=self.track_time, width=5, font=('Times', 12)) 137 | self.time_display.grid(row=1, column=1) 138 | 139 | def load(self): 140 | self.music_file = filedialog.askopenfilename() 141 | 142 | def play(self): 143 | if self.music_file: 144 | pygame.mixer.init() 145 | pygame.mixer.music.load(self.music_file) 146 | pygame.mixer.music.play() 147 | self.update_time() 148 | 149 | def pause(self): 150 | if not self.playing_state: 151 | pygame.mixer.music.pause() 152 | self.playing_state = True 153 | else: 154 | pygame.mixer.music.unpause() 155 | self.playing_state = False 156 | 157 | def stop(self): 158 | pygame.mixer.music.stop() 159 | 160 | def update_time(self): 161 | if pygame.mixer.music.get_busy(): 162 | time = pygame.mixer.music.get_pos() // 1000 163 | mins = time // 60 164 | secs = time % 60 165 | self.track_time.set(f'{mins:02d}:{secs:02d}') 166 | root.after(1000, self.update_time) 167 | 168 | 169 | #========================================================================================================= 170 | #========================================================================================================= 171 | 172 | # A U D I O U T I L I T I E S 173 | 174 | # Global variables for the first part 175 | audio = None 176 | file_path = None 177 | 178 | # Global variables for the second part 179 | audio_data = None 180 | processed_audio = None 181 | samplerate = None 182 | num_channels = None 183 | 184 | # General Audio Functions 185 | def load_audio_general(): 186 | global audio, file_path 187 | file_path = filedialog.askopenfilename() 188 | if file_path: 189 | audio = AudioSegment.from_file(file_path, format="wav") 190 | 191 | 192 | def save_audio(suffix): 193 | global audio, file_path 194 | if audio and file_path: 195 | base_name, ext = os.path.splitext(file_path) 196 | new_file_path = f"{base_name}_{suffix}{ext}" 197 | audio.export(new_file_path, format="wav") 198 | print(f"Saved as {new_file_path}") 199 | 200 | def normalize(): 201 | global audio 202 | if audio: 203 | audio = audio.normalize() 204 | print("Normalized") 205 | save_audio('normalized') 206 | 207 | def stereo_to_mono(): 208 | global audio 209 | if audio: 210 | audio = audio.set_channels(1) 211 | print("Converted to Mono") 212 | save_audio('stereotomono') 213 | 214 | # Function to convert mono to stereo 215 | def mono_to_stereo(): 216 | global audio 217 | if audio: 218 | audio = audio.set_channels(2) 219 | print("Converted to Stereo") 220 | save_audio('monotostereo') 221 | 222 | # Function to reverse audio 223 | def reverse_audio(): 224 | global audio 225 | if audio: 226 | audio = audio.reverse() 227 | print("Reversed") 228 | save_audio('reversed') 229 | 230 | def adjust_gain(value): 231 | global audio 232 | if audio: 233 | audio = audio._spawn(audio.raw_data, overrides={ 234 | "frame_rate": int(audio.frame_rate * float(value)) 235 | }).set_frame_rate(audio.frame_rate) 236 | print(f"Gain adjusted to: {value}") 237 | def change_bit_rate(event): 238 | global audio 239 | selected_bit_rate = bit_rate_combo.get() 240 | if audio: 241 | if selected_bit_rate == '16': 242 | audio = audio.set_sample_width(2) 243 | elif selected_bit_rate == '24': 244 | audio = audio.set_sample_width(3) 245 | elif selected_bit_rate == '32': 246 | audio = audio.set_sample_width(4) 247 | print(f"Bit rate changed to {selected_bit_rate} bits") 248 | save_audio(f'bitrate{selected_bit_rate}') 249 | 250 | def change_sample_rate(event): 251 | global audio 252 | if audio: 253 | selected_sample_rate = sample_rate_combo.get() 254 | audio = audio.set_frame_rate(int(selected_sample_rate)) 255 | print(f"Sample rate changed to {selected_sample_rate} Hz") 256 | save_audio(f'samplerate{selected_sample_rate}') 257 | 258 | def adjust_fade_in(value): 259 | global audio 260 | if audio: 261 | audio = audio.fade_in(int(value)) 262 | print(f"Fade-in time set to: {value} ms") 263 | save_audio(f'fadein{value}') 264 | 265 | def adjust_fade_out(value): 266 | global audio 267 | if audio: 268 | audio = audio.fade_out(int(value)) 269 | print(f"Fade-out time set to: {value} ms") 270 | save_audio(f'fadeout{value}') 271 | 272 | def adjust_level(value): 273 | global audio 274 | if audio: 275 | # Implement your level adjustment logic here 276 | print(f"Level adjusted to: {value}") 277 | save_audio(f'level{value}') 278 | 279 | def create_audio_frame(master): 280 | audio_frame = tk.Frame(master, relief='groove', borderwidth=5) 281 | audio_frame.grid(row=0, column=1, padx=10, pady=10) 282 | 283 | tk.Button(audio_frame, text='Load Audio', command=load_audio).grid(row=0, column=0, columnspan=2) 284 | 285 | # Normalize and Reverse buttons on the same row 286 | tk.Button(audio_frame, text='Normalize', command=normalize).grid(row=1, column=0) 287 | tk.Button(audio_frame, text='Reverse Audio', command=reverse_audio).grid(row=1, column=1) 288 | 289 | # Stereo and Mono buttons on the same row 290 | tk.Button(audio_frame, text='Stereo to Mono', command=stereo_to_mono).grid(row=2, column=0) 291 | tk.Button(audio_frame, text='Mono to Stereo', command=mono_to_stereo).grid(row=2, column=1) 292 | 293 | # Bit rate and Sample rate dropdowns on the same row 294 | bit_rate_combo = ttk.Combobox(audio_frame, values=["16", "24", "32"]) 295 | bit_rate_combo.bind("<>", change_bit_rate) 296 | bit_rate_combo.grid(row=3, column=0) 297 | 298 | sample_rate_combo = ttk.Combobox(audio_frame, values=["44100", "48000", "96000"]) 299 | sample_rate_combo.bind("<>", change_sample_rate) 300 | sample_rate_combo.grid(row=3, column=1) 301 | 302 | # Level adjustment slider 303 | tk.Scale(audio_frame, from_=0, to=100, orient='horizontal', label='Level', 304 | command=adjust_level).grid(row=4, column=0, columnspan=2) 305 | 306 | # Fade in/out sliders on the same row 307 | tk.Scale(audio_frame, from_=0, to=10000, orient='horizontal', label='Fade In', 308 | command=adjust_fade_in).grid(row=5, column=0) 309 | 310 | tk.Scale(audio_frame, from_=0, to=10000, orient='horizontal', label='Fade Out', 311 | command=adjust_fade_out).grid(row=5, column=1) 312 | 313 | return audio_frame 314 | 315 | #========================================================================================================= 316 | #========================================================================================================= 317 | 318 | # U T I L I T Y E F F E C T S 319 | 320 | 321 | # Functions related to Pedalboard and basic audio IO 322 | def load_audio(): 323 | filepath = filedialog.askopenfilename(title="Select an audio file", 324 | filetypes=[("WAV files", "*.wav"), 325 | ("All files", "*.*")]) 326 | if not filepath: 327 | return 328 | 329 | with AudioFile(filepath) as f: 330 | global audio_data, samplerate, num_channels 331 | audio_data = f.read(f.frames) 332 | samplerate = f.samplerate 333 | num_channels = f.num_channels 334 | 335 | def process_audio(): 336 | if audio_data is None: 337 | print("No audio data loaded.") 338 | return 339 | 340 | board = Pedalboard([Chorus(), Reverb(room_size=0.25)]) 341 | global processed_audio 342 | processed_audio = board(audio_data, samplerate, reset=False) 343 | 344 | def save_audio(): 345 | if processed_audio is None: 346 | print("No processed audio to save.") 347 | return 348 | 349 | filepath = filedialog.asksaveasfilename(defaultextension=".wav", 350 | filetypes=[("WAV files", "*.wav"), 351 | ("All files", "*.*")]) 352 | if not filepath: 353 | return 354 | 355 | with AudioFile(filepath, 'w', samplerate, num_channels) as f: 356 | f.write(processed_audio) 357 | 358 | # Functions related to PyDub effects 359 | def apply_effects(): 360 | global audio_data 361 | audio = AudioSegment._spawn(audio_data, num_channels=num_channels, sample_width=2, frame_rate=samplerate) 362 | if reverb_var.get(): 363 | audio = apply_reverb(audio) 364 | if chorus_var.get(): 365 | audio = apply_chorus(audio) 366 | if delay_var.get(): 367 | audio = apply_delay(audio) 368 | if flanger_var.get(): 369 | audio = apply_flanger(audio) 370 | if distortion_var.get(): 371 | audio = apply_distortion(audio) 372 | play(audio) 373 | 374 | def apply_reverb(audio): 375 | return audio.overlay(audio._spawn(audio.raw_data, shift=500), gain_during_overlay=-10) 376 | 377 | def apply_chorus(audio): 378 | return audio.overlay(audio._spawn(audio.raw_data, shift=150)).overlay(audio._spawn(audio.raw_data, shift=300)) 379 | 380 | def apply_delay(audio): 381 | silence = AudioSegment.silent(duration=1000) 382 | return audio + silence + audio 383 | 384 | def apply_flanger(audio): 385 | flanger = audio._spawn(audio.raw_data, shift=50) 386 | return (audio + flanger) 387 | 388 | def apply_distortion(audio): 389 | return audio.apply_gain(20).limiter(gain=10) 390 | 391 | 392 | #========================================================================================================= 393 | #========================================================================================================= 394 | 395 | 396 | # T E X T T O A U D I O 397 | 398 | def download_file(url, filename): 399 | r = requests.get(url, allow_redirects=True) 400 | open(filename, 'wb').write(r.content) 401 | 402 | def threaded_generate_music(input_audio_file=None, duration=8, continuation=False): 403 | global model_dropdown # Declare it as global 404 | selected_model = model_dropdown.get() # Get the selected model from the dropdown 405 | 406 | input_text = text_input.get("1.0", "end-1c") 407 | api_keys = load_api_keys() 408 | if api_keys: 409 | os.environ["REPLICATE_API_TOKEN"] = api_keys.get('replicate_api_key', '') 410 | 411 | # Create input dictionary 412 | model_input = { 413 | "prompt": input_text, 414 | "duration": 20 415 | } 416 | 417 | # Run the selected model 418 | if selected_model == 'meta/musicgen': 419 | model_id = "meta/musicgen:7a76a8258b23fae65c5a22debb8841d1d7e816b75c2f24218cd2bd8573787906" 420 | else: 421 | model_id = "allenhung1025/looptest:0de4a5f14b9120ce02c590eb9cf6c94841569fafbc4be7ab37436ce738bcf49f" 422 | 423 | output = replicate.run(model_id, input=model_input) 424 | 425 | download_url = output # Assuming output is a string that is a URL 426 | 427 | # Get current timestamp and format it 428 | timestamp = datetime.now().strftime("%Y%m%d%H%M%S") 429 | 430 | # Create a filename using the text input and timestamp 431 | sanitized_input_text = "".join(e for e in input_text if e.isalnum()) 432 | filename = f"{sanitized_input_text[:30]}_{timestamp}.wav" 433 | 434 | print(f"Downloading from {download_url}...") 435 | download_file(download_url, filename) 436 | print(f"Download complete: saved as {filename}") 437 | 438 | # Function to generate music using the replicate API 439 | def generate_music(input_audio_file=None, duration=8, continuation=False): 440 | generate_thread = threading.Thread(target=threaded_generate_music, args=(input_audio_file, duration, continuation)) 441 | generate_thread.start() 442 | 443 | OUTPUT_DIR = "random_sounds" 444 | 445 | #==================================================================================== 446 | #==================================================================================== 447 | 448 | # A L G O R I T H M I C E F F E C T S 449 | def add_delay(segment): 450 | delayed = segment 451 | for _ in range(random.randint(1, 3)): 452 | delayed = delayed.overlay(segment, gain_during_overlay=random.randint(-15, -1)) 453 | return delayed._spawn(b"\0" * int(44.1 * random.randint(50, 700))) 454 | 455 | def apply_stutter(segment): 456 | s_point, d_ms = random.randint(0, len(segment) - 150), random.randint(10, 200) 457 | stutter_piece = segment[s_point:s_point + d_ms] 458 | return sum([stutter_piece] * random.randint(1, 15)) 459 | 460 | def apply_arpeggio(frequency, generator): 461 | step, steps = random.choice([50, 75, 100, 125]), random.randint(3, 8) 462 | dur = random.randint(50, 200) 463 | return sum([generator(frequency + i * step).to_audio_segment(dur) for i in range(steps)]) 464 | 465 | def randomized_arpeggiation(base_freq, steps, dur): 466 | gen_choice, steps = random.choice([Sine, Square, Sawtooth, Triangle, Pulse]), random.sample(steps, len(steps)) 467 | return sum([gen_choice(base_freq * step).to_audio_segment(dur) for step in steps]) 468 | 469 | def makeshift_echo(sound, delay_time, decay_factor): 470 | delay = AudioSegment.silent(delay_time) 471 | delayed = sound.overlay(sound + decay_factor, position=delay_time) 472 | return sound + delay + delayed 473 | 474 | def makeshift_reverb(sound, num=5, delay=30, decay=-5): 475 | for _ in range(num): 476 | sound = makeshift_echo(sound, delay, decay) 477 | delay, decay = int(delay * 1.2), decay - 2.5 478 | return sound 479 | 480 | #========================================================================================================= 481 | #========================================================================================================= 482 | 483 | # A L G O R I T H M I C C O M P O S I T I O N 484 | 485 | # generate 486 | def generate_random_sound(filename, randomness_factor=0.5, max_duration=6000): # <-- Add max_duration parameter here 487 | 488 | generators = [Sine, Square, Sawtooth, Triangle, Pulse] 489 | gen_choice = random.choice(generators) 490 | 491 | # Random frequency between 50Hz and 880Hz 492 | freq = random.randint(50, 600) 493 | 494 | # Random duration between 0.4s and 3s in milliseconds 495 | duration = random.randint(400, 3000) 496 | 497 | # Generate the first sound 498 | sound1 = gen_choice(freq).to_audio_segment(duration=duration) 499 | 500 | # With a probability dictated by randomness_factor, generate a second sound and concatenate 501 | sound = sound1 502 | if random.random() < randomness_factor: 503 | max_duration2 = max_duration - duration 504 | if max_duration2 > 400: 505 | duration2 = random.randint(400, max_duration2) 506 | gen_choice2 = random.choice(generators) 507 | freq2 = random.randint(50, 880) 508 | sound2 = gen_choice2(freq2).to_audio_segment(duration=duration2) 509 | sound += sound2 510 | 511 | # Random frequency between 400Hz and 900Hz 512 | freq = random.randint(50, 880) 513 | 514 | # Random duration between 0.4s and 6s in milliseconds 515 | duration = random.randint(400, 3000) 516 | 517 | # Generate the first sound 518 | sound1 = gen_choice(freq).to_audio_segment(duration=duration) 519 | 520 | # With a 50% probability, generate a second sound and concatenate 521 | sound = sound1 522 | if random.random() > 0.8: 523 | max_duration2 = 1000 - duration 524 | if max_duration2 > 400: 525 | duration2 = random.randint(400, max_duration2) 526 | gen_choice2 = random.choice(generators) 527 | freq2 = random.randint(400, 900) 528 | sound2 = gen_choice2(freq2).to_audio_segment(duration=duration2) 529 | sound += sound2 530 | 531 | # Apply random effects 532 | if random.random() > (0.7 - randomness_factor/2): 533 | sound = sound + sound.reverse() 534 | 535 | if random.random() > (0.6 - randomness_factor/2): 536 | sound = add_delay(sound) 537 | 538 | if random.random() > (0.7 - randomness_factor/2): 539 | sound = apply_stutter(sound) 540 | 541 | if random.random() > (0.6 - randomness_factor/2): 542 | sound = apply_arpeggio(freq, gen_choice) 543 | 544 | if random.random() < (0.5 - randomness_factor/2): 545 | speed_change = random.uniform(1.1, 1.5) 546 | if len(sound) > 150: 547 | sound = sound.speedup(playback_speed=speed_change) 548 | else: 549 | sound = sound.speedup(playback_speed=speed_change, chunk_size=int(len(sound)/2)) 550 | 551 | if random.random() > (0.6 - randomness_factor/2): 552 | sound = sound.fade_in(duration=1000) 553 | 554 | if random.random() > (0.6 - randomness_factor/2): 555 | sound = sound.fade_out(duration=1000) 556 | 557 | if random.random() > (0.6 - randomness_factor/2): 558 | sound = sound.invert_phase() 559 | 560 | if random.random() > (0.6 - randomness_factor/2): 561 | cutoff = random.choice([300, 500, 1000, 2000]) 562 | filter_choice = random.choice(['highpass', 'lowpass']) 563 | if filter_choice == 'highpass': 564 | sound = sound.high_pass_filter(cutoff) 565 | else: 566 | sound = sound.low_pass_filter(cutoff) 567 | 568 | if random.random() > (0.5 - randomness_factor/2): 569 | steps = [1, 9/8, 5/4, 3/2] 570 | duration_per_step = random.randint(100, 500) 571 | sound += randomized_arpeggiation(freq, steps, duration_per_step) 572 | 573 | if random.random() > (0.7 - randomness_factor/2): 574 | delay_time = random.randint(100, 500) 575 | decay_factor = random.uniform(-2, -5) 576 | sound += makeshift_echo(sound, delay_time, decay_factor) 577 | 578 | if random.random() > (0.7 - randomness_factor/2): 579 | sound += makeshift_reverb(sound) 580 | 581 | # At the end, before exporting: 582 | if len(sound) > max_duration: 583 | sound = sound[:max_duration] # Trim to desired length. You can also add a fade out for smoother ending. 584 | 585 | sound.export(filename, format="wav") 586 | 587 | #========================================================================================================= 588 | #========================================================================================================= 589 | 590 | # A L G O R I T H M I C P E R C U S S I O N 591 | 592 | class DrumLoopGenerator: 593 | def __init__(self, tempo=120, beat_length=16, max_duration=40000): # Updated beat_length to 16 for 4 bars 594 | self.tempo = tempo 595 | self.beat_length = beat_length 596 | self.max_duration = max_duration 597 | 598 | # Mapping of drum sound generators and their likelihoods 599 | self.drum_generators = { 600 | 'kick': {'func': self._generate_kick, 'likelihood': 0.2, 'volume': 5}, # Increased volume for kick 601 | 'snare': {'func': self._generate_snare, 'likelihood': 0.2, 'volume': 0}, 602 | 'hihat': {'func': self._generate_hihat, 'likelihood': 0.2, 'volume': 0}, 603 | 'tom': {'func': self._generate_tom, 'likelihood': 0.1, 'volume': 0}, 604 | 'silence': {'func': self._generate_silence, 'likelihood': 0.2, 'volume': 0} 605 | } 606 | 607 | def _generate_sound(self, freq_range, noise=False, pitch_factor=0.8): # Lowered the pitch 608 | freq = random.uniform(*freq_range) * pitch_factor 609 | duration = 100 610 | sound = Sine(freq).to_audio_segment(duration=duration) 611 | if noise: 612 | sound += WhiteNoise().to_audio_segment(duration=duration) 613 | return sound 614 | 615 | def _generate_kick(self): 616 | sound = self._generate_sound((40, 80)) 617 | return sound + self.drum_generators['kick']['volume'] # Increased volume 618 | 619 | def _generate_snare(self): 620 | return self._generate_sound((600, 2000), noise=True) 621 | 622 | def _generate_hihat(self): 623 | return WhiteNoise().to_audio_segment(duration=25) 624 | 625 | def _generate_tom(self): 626 | freq_range = random.choice([(100, 150), (150, 250), (250, 350)]) 627 | return self._generate_sound(freq_range) 628 | 629 | def _generate_silence(self): 630 | return AudioSegment.silent(duration=100) 631 | 632 | def _randomly_apply_effects(self, sound): 633 | # More modular and dynamic effects application 634 | effects = [self._apply_reverb, self._apply_echo] 635 | for effect in effects: 636 | if random.random() < 0.5: 637 | sound = effect(sound) 638 | return sound 639 | 640 | def _apply_reverb(self, sound): 641 | # Placeholder reverb effect 642 | return sound + sound.reverse() 643 | 644 | def _apply_echo(self, sound): 645 | # Placeholder echo effect 646 | return sound + sound.overlay(sound) 647 | 648 | def generate_loop(self, filename): 649 | beat_duration = 60000 / self.tempo 650 | choices = list(self.drum_generators.keys()) 651 | 652 | loop = sum( 653 | self.drum_generators['kick']['func']() if i % 4 == 0 else # Make the kick fall on 4/4 654 | self.drum_generators[random.choices(choices, [v['likelihood'] for v in self.drum_generators.values()])[0]][ 655 | 'func']() 656 | if random.random() < 0.8 else AudioSegment.silent(duration=beat_duration) 657 | for i in range(self.beat_length) 658 | )[:self.max_duration] 659 | 660 | # Removed fades for seamless looping 661 | 662 | # Generate a timestamp-based filename 663 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") 664 | unique_filename = f"drumloop_{timestamp}.wav" 665 | 666 | loop.export(unique_filename, format="wav") 667 | 668 | 669 | #========================================================================================================= 670 | #========================================================================================================= 671 | 672 | 673 | # S O U N D P A C K S A V E 674 | 675 | def main(num_sounds, prefix, randomness_factor=0.5, max_duration=6000): # <-- Add max_duration parameter here 676 | 677 | # Generate a timestamp 678 | timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") 679 | 680 | # Create a new directory inside OUTPUT_DIR with "sound pack [timestamp]" as the name 681 | new_output_dir = os.path.join(OUTPUT_DIR, f"sound pack {timestamp}") 682 | 683 | if not os.path.exists(new_output_dir): 684 | os.makedirs(new_output_dir) 685 | 686 | for i in range(num_sounds): 687 | output_file = os.path.join(new_output_dir, f"{prefix}_{i}.wav") 688 | generate_random_sound(output_file, randomness_factor, max_duration) # <-- pass max_duration here 689 | print(f"Generated {output_file}") 690 | 691 | #========================================================================================================= 692 | #========================================================================================================= 693 | 694 | 695 | # m i d i m a k e r 696 | class SongGeneratorGUI: 697 | def __init__(self, root): 698 | self.root = root 699 | self.chord_progression = [] 700 | 701 | 702 | # Create the GUI elements 703 | self.key_label = tk.Label(root, text="Select Key:") 704 | self.key_label.pack() 705 | 706 | self.key_var = tk.StringVar(root) 707 | self.key_var.set("C") # Default key is C 708 | self.key_option_menu = tk.OptionMenu(root, self.key_var, *keys) 709 | self.key_option_menu.pack() 710 | 711 | self.song_name_entry = tk.Entry(root) 712 | self.song_name_entry.pack() 713 | 714 | self.randomize_text_button = tk.Button(root, text="Randomize Text", command=self.randomize_text) 715 | self.randomize_text_button.pack() 716 | 717 | self.randomize_chords_button = tk.Button(root, text="Randomize Chords", command=self.randomize_chords) 718 | self.randomize_chords_button.pack() 719 | 720 | self.generate_button = tk.Button(root, text="Generate", command=self.generate_song) 721 | self.generate_button.pack() 722 | 723 | def generate_song(self): 724 | try: 725 | timestamp = datetime.now().strftime("%Y%m%d%H%M%S") 726 | new_folder = f"random_midi_{timestamp}" 727 | os.makedirs(new_folder, exist_ok=True) 728 | 729 | song_name = self.song_name_entry.get().strip() 730 | if not song_name: 731 | return 732 | 733 | key = self.key_var.get() 734 | scale = scales.get(key, []) 735 | midi = MIDIFile(1) 736 | 737 | midi.addTrackName(track=0, time=0, trackName=song_name) 738 | midi.addTempo(track=0, time=0, tempo=120) 739 | 740 | time = 0 741 | 742 | for chord_name in self.chord_progression: 743 | chord_notes = chords.get(chord_name, []) 744 | for note_index in chord_notes: 745 | note = (note_index + scale[0]) % 12 746 | midi.addNote(track=0, channel=0, pitch=note, time=time, duration=1, volume=100) 747 | time += 0.5 # Advance time for the next chord 748 | 749 | filename = f"{song_name.replace(' ', '_').lower()}.mid" 750 | filepath = os.path.join(new_folder, filename) 751 | with open(filepath, "wb") as output_file: 752 | midi.writeFile(output_file) 753 | except Exception as e: 754 | print(f"Error: {e}") 755 | 756 | def randomize_text(self): 757 | randomized_name = random.choice(song_names_list) 758 | self.song_name_entry.delete(0, tk.END) 759 | self.song_name_entry.insert(0, randomized_name) 760 | 761 | def randomize_chords(self): 762 | chord_names = list(chords.keys()) 763 | self.chord_progression = [random.choice(chord_names) for _ in range(8)] 764 | 765 | def run(self): 766 | self.root.mainloop() 767 | 768 | 769 | def get_chord_notes(chord_name, key, scale): 770 | root_note = notes[key] 771 | root_index = scale.index(root_note) 772 | 773 | chord_notes = [] 774 | for step in chords[chord_name]: 775 | note_index = (root_index + step) % len(scale) 776 | chord_notes.append(scale[note_index]) 777 | 778 | return chord_notes 779 | 780 | 781 | notes = { 782 | "C": "C", 783 | "C#": "C#", 784 | "Db": "C#", 785 | "D": "D", 786 | "D#": "D#", 787 | "Eb": "D#", 788 | "E": "E", 789 | "F": "F", 790 | "F#": "F#", 791 | "Gb": "F#", 792 | "G": "G", 793 | "G#": "G#", 794 | "Ab": "G#", 795 | "A": "A", 796 | "A#": "A#", 797 | "Bb": "A#", 798 | "B": "B" 799 | } 800 | 801 | keys = ['C', 'C#', 'Db', 'D', 'D#', 'Eb', 'E', 'F', 'F#', 'Gb', 'G', 'G#', 'Ab', 'A', 'A#', 'Bb', 'B'] 802 | 803 | 804 | scales = { 805 | "C": [0, 2, 4, 5, 7, 9, 11], 806 | "C#": [1, 3, 5, 6, 8, 10, 0], 807 | "Db": [1, 3, 5, 6, 8, 10, 0], 808 | "D": [2, 4, 6, 7, 9, 11, 1], 809 | "D#": [3, 5, 7, 8, 10, 0, 2], 810 | "Eb": [3, 5, 7, 8, 10, 0, 2], 811 | "E": [4, 6, 8, 9, 11, 1, 3], 812 | "F": [5, 7, 9, 10, 0, 2, 4], 813 | "F#": [6, 8, 10, 11, 1, 3, 5], 814 | "Gb": [6, 8, 10, 11, 1, 3, 5], 815 | "G": [7, 9, 11, 0, 2, 4, 6], 816 | "G#": [8, 10, 0, 1, 3, 5, 7], 817 | "Ab": [8, 10, 0, 1, 3, 5, 7], 818 | "A": [9, 11, 1, 2, 4, 6, 8], 819 | "A#": [10, 0, 2, 3, 5, 7, 9], 820 | "Bb": [10, 0, 2, 3, 5, 7, 9], 821 | "B": [11, 1, 3, 4, 6, 8, 10] 822 | } 823 | 824 | chords = { 825 | "Major": [0, 4, 7], 826 | "Minor": [0, 3, 7], 827 | "Diminished": [0, 3, 6], 828 | "Augmented": [0, 4, 8], 829 | "Suspended2": [0, 2, 7], 830 | "Suspended4": [0, 5, 7], 831 | "Major7": [0, 4, 7, 11], 832 | "Minor7": [0, 3, 7, 10], 833 | "Dominant7": [0, 4, 7, 10], 834 | "Diminished7": [0, 3, 6, 9], 835 | "HalfDiminished7": [0, 3, 6, 10], 836 | "Augmented7": [0, 4, 8, 10], 837 | "Sixth": [0, 4, 7, 9], 838 | "MinorSixth": [0, 3, 7, 9], 839 | } 840 | 841 | # Comprehensive Extended Chord Progressions with 10 Options per Section 842 | chord_progressions = { 843 | "Intro": [ 844 | ["Minor", "Minor7"], ["Major", "Major7"], ["Minor", "Minor"], ["Diminished", "Minor"], 845 | ["Major", "Add9"], ["Minor", "Minor7"], ["Major", "Major"], ["Major", "Dominant7"], 846 | ["Minor", "Minor6"], ["Major", "Major7"] 847 | ], 848 | "Verse": [ 849 | ["Minor", "Minor"], ["Major7", "Dominant7"], ["Minor", "Minor6"], ["Minor", "Minor"], 850 | ["Major", "Add9"], ["Minor", "Minor7"], ["Major", "Major"], ["Major", "Dominant7"], 851 | ["Minor", "Minor6"], ["Major", "Major7"] 852 | ], 853 | "PreChorus": [ 854 | ["Major", "Major"], ["Minor", "Minor"], ["Major", "Major7"], ["Major", "Major"], 855 | ["Minor", "Minor"], ["Major7", "Dominant7"], ["Minor", "Minor6"], ["Minor", "Minor"], 856 | ["Major", "Add9"], ["Minor", "Minor7"] 857 | ], 858 | "Chorus": [ 859 | ["Major", "Add9"], ["Minor", "Minor7"], ["Major", "Major"], ["Major", "Dominant7"], 860 | ["Minor", "Minor"], ["Major7", "Dominant7"], ["Minor", "Minor6"], ["Minor", "Minor"], 861 | ["Major", "Add9"], ["Minor", "Minor7"] 862 | ], 863 | "PostChorus": [ 864 | ["Major", "Major"], ["Major", "Add9"], ["Minor", "Minor"], ["Major", "Major"], 865 | ["Minor", "Minor"], ["Major7", "Dominant7"], ["Minor", "Minor6"], ["Minor", "Minor"], 866 | ["Major", "Add9"], ["Minor", "Minor7"] 867 | ], 868 | "Bridge": [ 869 | ["Minor7", "Minor"], ["Minor", "Minor"], ["Major", "Major7"], ["Diminished", "HalfDiminished7"], 870 | ["Major", "Add9"], ["Minor", "Minor7"], ["Major", "Major"], ["Major", "Dominant7"], 871 | ["Minor", "Minor6"], ["Major", "Major7"] 872 | ], 873 | "Breakdown": [ 874 | ["Major", "Major"], ["Minor", "Minor"], ["Diminished", "Minor"], ["Augmented", "Major"], 875 | ["Minor", "Minor"], ["Major7", "Dominant7"], ["Minor", "Minor6"], ["Minor", "Minor"], 876 | ["Major", "Add9"], ["Minor", "Minor7"] 877 | ], 878 | "Outro": [ 879 | ["Minor", "Minor"], ["Minor", "Minor6"], ["Major7", "Major"], ["Minor", "Diminished"], 880 | ["Major", "Add9"], ["Minor", "Minor7"], ["Major", "Major"], ["Major", "Dominant7"], 881 | ["Minor", "Minor6"], ["Major", "Major7"] 882 | ], 883 | "AltOutro": [ 884 | ["Major", "Major"], ["Major7", "Major"], ["Minor", "Minor7"], ["Diminished", "HalfDiminished7"], 885 | ["Major", "Add9"], ["Minor", "Minor7"], ["Major", "Major"], ["Major", "Dominant7"], 886 | ["Minor", "Minor6"], ["Major", "Major7"] 887 | ] 888 | } 889 | 890 | 891 | song_structures = [ 892 | ["Intro"], 893 | ["Verse"], 894 | ["Chorus"], 895 | ["Verse"], 896 | ["Chorus"], 897 | ["Bridge"], 898 | ["Chorus", "Chorus"], 899 | ["Outro"] 900 | ] 901 | 902 | song_names_list = [ 903 | "Cosmic Spaghetti", 904 | "Temporal Jiggle", 905 | "Electric Moonwalk", 906 | "Limbo of Laughter", 907 | "Funky Chicken Fandango", 908 | "Whiskey Tango Foxtrot", 909 | "Cat Meme Cathedral", 910 | "The Subtle Art of Not Giving a Meow", 911 | "Cereal Killer Diaries", 912 | "The Yawning Chasm of Adulthood" 913 | 914 | ] 915 | 916 | 917 | #========================================================================================================= 918 | #========================================================================================================= 919 | #========================================================================================================= 920 | #========================================================================================================= 921 | 922 | # G U I 923 | 924 | # G U I 925 | 926 | # G U I 927 | 928 | #========================================================================================================= 929 | #========================================================================================================= 930 | #========================================================================================================= 931 | 932 | 933 | def start_gui(): 934 | global model_dropdown # Declare it as global 935 | root = tk.Tk() 936 | root.title("Soundstorm") 937 | 938 | # Add the audio frame at row 2, column 0 939 | create_audio_frame(root) 940 | 941 | # Create a frame with a thin border 942 | frame = tk.Frame(root, bd=1, relief="solid") # bd is the border width, relief specifies the border type 943 | 944 | # Place your widgets inside the frame 945 | ttk.Label(frame, text="What sort of song or sound do you want to hear? ").pack(pady=5) 946 | global text_input 947 | text_input = tk.Text(frame, height=3, width=30) 948 | text_input.pack(pady=5) 949 | 950 | # Create and pack the model dropdown 951 | model_choices = ['meta/musicgen', 'allenhung1025/looptest'] 952 | model_dropdown = ttk.Combobox(frame, values=model_choices) 953 | model_dropdown.set(model_choices[0]) # Default choice 954 | model_dropdown.pack(pady=5) 955 | 956 | # Add a new button to trigger random GPT prompt 957 | gpt_button = tk.Button(frame, text="Random GPT Prompt", command=generate_random_prompt) 958 | gpt_button.pack(pady=5) 959 | 960 | generate_button = ttk.Button(frame, text="Generate AI Audio", command=generate_music) 961 | generate_button.pack(pady=5) 962 | 963 | frame.grid(row=0, column=0, padx=5, pady=5) 964 | 965 | # ======================================================================================================== 966 | # ======================================================================================================== 967 | 968 | # p l a y b a c k f r a m e 969 | 970 | music_frame = tk.Frame(root) 971 | music_frame.grid(row=0, column=3) 972 | 973 | # Initialize MusicPlayer with the frame 974 | player = MusicPlayer(music_frame) 975 | 976 | # ======================================================================================================== 977 | # ======================================================================================================== 978 | 979 | # A L G O R I T H M I C C O M P O S I T I O N g u i 980 | 981 | # Combined Sound FX and Save Settings Frame 982 | combined_fx_save_frame = tk.LabelFrame(root, text="Algorithmic Composition", padx=10, pady=10) 983 | combined_fx_save_frame.grid(row=1, column=1, padx=5, pady=5) 984 | 985 | # How many files 986 | tk.Label(combined_fx_save_frame, text="How many files?").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5) 987 | num_sounds_entry = tk.Entry(combined_fx_save_frame) 988 | num_sounds_entry.grid(row=0, column=1, padx=5, pady=5) 989 | num_sounds_entry.insert(0, "8") 990 | 991 | # Max Duration 992 | tk.Label(combined_fx_save_frame, text="Max duration (in milliseconds):").grid(row=1, column=0, sticky=tk.W, padx=5, 993 | pady=5) 994 | length_slider = tk.Scale(combined_fx_save_frame, from_=400, to=6000, resolution=100, orient=tk.HORIZONTAL) 995 | length_slider.grid(row=1, column=1, padx=5, pady=5) 996 | length_slider.set(6000) 997 | 998 | # Randomness Factor 999 | tk.Label(combined_fx_save_frame, text="How random?").grid(row=2, column=0, sticky=tk.W, padx=5, pady=5) 1000 | randomness_slider = tk.Scale(combined_fx_save_frame, from_=0, to=1, resolution=0.01, orient=tk.HORIZONTAL) 1001 | randomness_slider.grid(row=2, column=1, padx=5, pady=5) 1002 | randomness_slider.set(0.5) 1003 | 1004 | # File name prefix 1005 | tk.Label(combined_fx_save_frame, text="File name prefix:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=5) 1006 | prefix_entry = tk.Entry(combined_fx_save_frame) 1007 | prefix_entry.grid(row=3, column=1, padx=5, pady=5) 1008 | prefix_entry.insert(0, "sound") 1009 | 1010 | def on_generate(): 1011 | try: 1012 | num_sounds = int(num_sounds_entry.get()) 1013 | prefix = prefix_entry.get() 1014 | randomness_factor = randomness_slider.get() 1015 | max_duration = length_slider.get() 1016 | main(num_sounds, prefix, randomness_factor) # Updated main call 1017 | messagebox.showinfo("Success", "Sounds generated successfully!") 1018 | except Exception as e: 1019 | messagebox.showerror("Error", str(e)) 1020 | 1021 | # Generate Randomly Coded Audio Button 1022 | generate_btn = tk.Button(combined_fx_save_frame, text="Code Audio", command=on_generate) 1023 | generate_btn.grid(row=4, columnspan=2, pady=5) # Placing it on the next available row in the grid 1024 | 1025 | # ======================================================================================================== 1026 | # ======================================================================================================== 1027 | 1028 | # Initialize DrumLoopGenerator within the frame 1029 | drum_loop_gen = DrumLoopGenerator() 1030 | 1031 | # Function to update DrumLoopGenerator parameters based on user input 1032 | def update_drum_loop_parameters(): 1033 | drum_loop_gen.tempo = int(tempo_slider.get()) 1034 | drum_loop_gen.beat_length = int(beat_length_slider.get()) 1035 | update_instrument_parameters('kick', kick_likelihood_slider, kick_volume_slider) 1036 | update_instrument_parameters('snare', snare_likelihood_slider, snare_volume_slider) 1037 | update_instrument_parameters('hihat', hihat_likelihood_slider, hihat_volume_slider) 1038 | status_label.config(text="Parameters Updated!") 1039 | 1040 | def update_instrument_parameters(instrument, likelihood_slider, volume_slider): 1041 | drum_loop_gen.drum_generators[instrument]['likelihood'] = likelihood_slider.get() 1042 | drum_loop_gen.drum_generators[instrument]['volume'] = volume_slider.get() 1043 | 1044 | # Function to trigger drum loop generation 1045 | def on_generate_drum_loop(): 1046 | drum_loop_gen.generate_loop("drum_loop.wav") 1047 | status_label.config(text="Drum Loop Generated!") 1048 | 1049 | # DrumLoopGenerator Frame 1050 | drum_loop_frame = tk.Frame(root, relief='groove', borderwidth=2) 1051 | drum_loop_frame.grid(row=1, column=2, padx=4, pady=4) 1052 | 1053 | # DrumLoopGenerator Label 1054 | tk.Label(drum_loop_frame, text="Drum Loop Generator").grid(row=0, columnspan=2) 1055 | 1056 | # User Customization for Drum Loop Parameters 1057 | tk.Label(drum_loop_frame, text="Customize Parameters").grid(row=1, columnspan=2) 1058 | 1059 | # Kick likelihood and volume sliders 1060 | tk.Label(drum_loop_frame, text="Kick").grid(row=2, column=0) 1061 | kick_likelihood_slider = tk.Scale(drum_loop_frame, from_=0.0, to=1.0, resolution=0.1, orient='horizontal') 1062 | kick_likelihood_slider.grid(row=2, column=1) 1063 | kick_likelihood_slider.set(0.3) 1064 | kick_volume_slider = tk.Scale(drum_loop_frame, from_=0.0, to=1.0, resolution=0.1, orient='horizontal') 1065 | kick_volume_slider.grid(row=2, column=2) 1066 | kick_volume_slider.set(0.5) 1067 | 1068 | # Snare likelihood and volume sliders 1069 | tk.Label(drum_loop_frame, text="Snare").grid(row=3, column=0) 1070 | snare_likelihood_slider = tk.Scale(drum_loop_frame, from_=0.0, to=1.0, resolution=0.1, orient='horizontal') 1071 | snare_likelihood_slider.grid(row=3, column=1) 1072 | snare_likelihood_slider.set(0.2) 1073 | snare_volume_slider = tk.Scale(drum_loop_frame, from_=0.0, to=1.0, resolution=0.1, orient='horizontal') 1074 | snare_volume_slider.grid(row=3, column=2) 1075 | snare_volume_slider.set(0.5) 1076 | 1077 | # Hihat likelihood and volume sliders 1078 | tk.Label(drum_loop_frame, text="Hihat").grid(row=4, column=0) 1079 | hihat_likelihood_slider = tk.Scale(drum_loop_frame, from_=0.0, to=1.0, resolution=0.1, orient='horizontal') 1080 | hihat_likelihood_slider.grid(row=4, column=1) 1081 | hihat_likelihood_slider.set(0.2) 1082 | hihat_volume_slider = tk.Scale(drum_loop_frame, from_=0.0, to=1.0, resolution=0.1, orient='horizontal') 1083 | hihat_volume_slider.grid(row=4, column=2) 1084 | hihat_volume_slider.set(0.5) 1085 | 1086 | # Tempo 1087 | tk.Label(drum_loop_frame, text="Tempo").grid(row=5, column=0) 1088 | tempo_slider = tk.Scale(drum_loop_frame, from_=60, to=240, orient='horizontal') 1089 | tempo_slider.grid(row=5, column=1, columnspan=2) 1090 | tempo_slider.set(120) 1091 | 1092 | # Beat Length 1093 | tk.Label(drum_loop_frame, text="Beat Length").grid(row=6, column=0) 1094 | beat_length_slider = tk.Scale(drum_loop_frame, from_=1, to=32, orient='horizontal') 1095 | beat_length_slider.grid(row=6, column=1, columnspan=2) 1096 | beat_length_slider.set(16) 1097 | 1098 | # Button to update parameters 1099 | update_parameters_button = tk.Button(drum_loop_frame, text="Update Parameters", command=update_drum_loop_parameters) 1100 | update_parameters_button.grid(row=7, columnspan=2) 1101 | 1102 | # Status label 1103 | status_label = tk.Label(drum_loop_frame, text="") 1104 | status_label.grid(row=9, columnspan=2) 1105 | 1106 | # DrumLoopGenerator Button to trigger generation (Only one button) 1107 | drum_loop_button = tk.Button(drum_loop_frame, text="Generate Drum Loop", command=on_generate_drum_loop) 1108 | drum_loop_button.grid(row=8, columnspan=2) 1109 | 1110 | 1111 | 1112 | # ======================================================================================================== 1113 | # ======================================================================================================== 1114 | 1115 | # New Audio Effects Frame with a thin border 1116 | audio_effects_frame = tk.Frame(root, bd=1, 1117 | relief="solid") # bd is the border width, relief specifies the border type 1118 | audio_effects_frame.grid(row=0, column=2, padx=10, pady=10) 1119 | 1120 | # Declare variables for check buttons if not already done 1121 | reverb_var = tk.IntVar() 1122 | chorus_var = tk.IntVar() 1123 | delay_var = tk.IntVar() 1124 | flanger_var = tk.IntVar() 1125 | distortion_var = tk.IntVar() 1126 | 1127 | load_button = ttk.Button(audio_effects_frame, text="Load Audio", command=load_audio) 1128 | load_button.grid(column=0, row=0) 1129 | 1130 | reverb_check = ttk.Checkbutton(audio_effects_frame, text='Reverb', variable=reverb_var) 1131 | reverb_check.grid(column=0, row=1, sticky=tk.W) 1132 | 1133 | chorus_check = ttk.Checkbutton(audio_effects_frame, text='Chorus', variable=chorus_var) 1134 | chorus_check.grid(column=0, row=2, sticky=tk.W) 1135 | 1136 | delay_check = ttk.Checkbutton(audio_effects_frame, text='Delay', variable=delay_var) 1137 | delay_check.grid(column=0, row=3, sticky=tk.W) 1138 | 1139 | flanger_check = ttk.Checkbutton(audio_effects_frame, text='Flanger', variable=flanger_var) 1140 | flanger_check.grid(column=0, row=4, sticky=tk.W) 1141 | 1142 | distortion_check = ttk.Checkbutton(audio_effects_frame, text='Distortion', variable=distortion_var) 1143 | distortion_check.grid(column=0, row=5, sticky=tk.W) 1144 | 1145 | # apply_button = ttk.Button(audio_effects_frame, text="Apply PyDub Effects", command=apply_effects) 1146 | # apply_button.grid(column=0, row=6) 1147 | 1148 | process_button = ttk.Button(audio_effects_frame, text="Process Audio with Pedalboard", command=process_audio) 1149 | process_button.grid(column=0, row=6) 1150 | 1151 | save_button = ttk.Button(audio_effects_frame, text="Save Audio", command=save_audio) 1152 | save_button.grid(column=0, row=7) 1153 | 1154 | # m i d i m a k e r G U I 1155 | 1156 | song_generator_frame = tk.Frame(root, relief='groove', borderwidth=2) 1157 | song_generator_frame.grid(row=1, column=0, padx=5, pady=5) 1158 | song_generator = SongGeneratorGUI(song_generator_frame) 1159 | 1160 | # ======================================================================================================== 1161 | # ======================================================================================================== 1162 | 1163 | # C H A T G U I 1164 | 1165 | 1166 | chat_frame = tk.LabelFrame(root, text="ChatGPT") 1167 | chat_frame.grid(row=1, column=3, padx=5, pady=5) 1168 | 1169 | # Text widget for chat display 1170 | global chat_display 1171 | chat_display = tk.Text(chat_frame, width=30, height=20, wrap=tk.WORD, state=tk.DISABLED) 1172 | chat_display.pack(pady=5) 1173 | 1174 | # Entry widget for user input 1175 | global user_input 1176 | user_input = tk.Entry(chat_frame, width=20) 1177 | user_input.pack(pady=5) 1178 | 1179 | # Button for sending message 1180 | send_button = tk.Button(chat_frame, text="Send", command=send_message) 1181 | send_button.pack(pady=5) 1182 | 1183 | 1184 | 1185 | root.mainloop() 1186 | 1187 | 1188 | 1189 | if __name__ == "__main__": 1190 | start_gui() 1191 | --------------------------------------------------------------------------------