Frank Faruk Logo

Micro Tuning of Individual Notes

Simple Description

The Decent Sampler Preset Tuner is a Python script designed to adjust the tuning for specified musical notes within a Decent Sampler .dspreset file. It allows users to apply individual tuning adjustments to multiple notes, update the background image, and provides flexible file handling through drag-and-drop support or a file selection dialog. The script ensures the original image format is preserved unless a different extension is specified.

Code

To begin, copy the Python code below. Then, open your chosen code editor (e.g., Sublime Text, Visual Studio Code) and paste the code into a new file. Save the file with a .py extension.

import xml.etree.ElementTree as ET

import sys
import os
import re
import tkinter as tk
from tkinter import filedialog


def select_file():
   # Check if a file path is provided via command-line arguments (for drag-and-drop)
   if len(sys.argv) > 1:
       file_path = sys.argv[1]
   else:
       # Create a file dialog for the user to select the file
       root = tk.Tk()
       root.withdraw()  # Hide the main window
       file_path = filedialog.askopenfilename(
           title="Select Decent Sampler .dspreset file",
           filetypes=[("Decent Sampler Preset", "*.dspreset"), ("XML Files", "*.xml"), ("All Files", "*.*")]
       )
   return file_path


def update_tuning(input_text, tuning_dict):
   for note, tuning_value in tuning_dict.items():
       # Escape any special regex characters in the note
       note_escaped = re.escape(note)
      
       # Build the note pattern using underscores to delimit the note
       pattern = rf'(]+name="[^"]*?_{note_escaped}(\d+)?_.*?"[^>]*?)(\s*/?>)'
      
       # Function to add or update tuning
       def add_tuning(match):
           tag_content = match.group(1)
           closing = match.group(3)
           if 'tuning="' not in tag_content:
               # Insert tuning attribute before the closing tag
               return f'{tag_content} tuning="{tuning_value}"{closing}'
           else:
               # Update existing tuning value
               updated_tag = re.sub(r'tuning="[^"]*"', f'tuning="{tuning_value}"', match.group(0))
               return updated_tag


       # Update the input text for the current note
       input_text = re.sub(pattern, add_tuning, input_text)
  
   return input_text


def update_bgImage(input_text, new_image_name):
   # Regular expression to find bgImage="Images/..." attribute
   pattern = r'(bgImage="Images/)([^"]*)(")'
  
   match = re.search(pattern, input_text)
   if match:
       original_image_name = match.group(2)
       original_extension = os.path.splitext(original_image_name)[1]  # Includes the dot, e.g., '.png'
   else:
       original_extension = '.png'  # Default to .png if not found


   if new_image_name != '0':
       # If the user did not specify an extension, use the original extension
       if not os.path.splitext(new_image_name)[1]:
           new_image_name += original_extension


       # Function to perform replacement
       def replace_bg_image(match):
           return f'{match.group(1)}{new_image_name}{match.group(3)}'


       # Replace the bgImage attribute value with the new image name
       updated_text = re.sub(pattern, replace_bg_image, input_text)
   else:
       # Do not change the input text
       updated_text = input_text


   return updated_text


def main():
   # Select the file
   file_path = select_file()


   if not file_path:
       print("No file selected. Exiting.")
       sys.exit(1)


   # Ask for the new image file name
   new_image_name = input("Enter the new image file name for bgImage (enter 0 to leave unchanged): ").strip()


   # Ask for the music notes and their tuning adjustments
   print("Enter the music notes and tuning adjustments in the format 'Note=TuningValue', separated by commas.")
   print("For example: G#=-0.51, A#=-0.25, C#=0.1")
   notes_input = input("Your input: ")


   # Parse the input into a dictionary
   tuning_dict = {}
   notes_list = [note.strip() for note in notes_input.split(',') if note.strip()]
   for note_pair in notes_list:
       if '=' in note_pair:
           note, tuning = note_pair.split('=')
           note = note.strip()
           tuning = tuning.strip()
           tuning_dict[note] = tuning
       else:
           print(f"Invalid format for note '{note_pair}'. Expected format is 'Note=TuningValue'.")
           sys.exit(1)


   try:
       # Read the content of the file
       with open(file_path, "r", encoding='utf-8') as file:
           content = file.read()


       # Update the content
       content = update_tuning(content, tuning_dict)
       content = update_bgImage(content, new_image_name)


       # Save the updated content back to a new file
       file_dir, file_name = os.path.split(file_path)
       file_base, file_ext = os.path.splitext(file_name)
       updated_file_name = f"{file_base}_updated{file_ext}"
       updated_file_path = os.path.join(file_dir, updated_file_name)


       with open(updated_file_path, "w", encoding='utf-8') as file:
           file.write(content)


       print(f"\nTuning updated for notes: {', '.join(tuning_dict.keys())}")
       if new_image_name != '0':
           print(f"bgImage updated to: {new_image_name}")
       else:
           print("bgImage unchanged.")
       print(f"Updated file saved as: {updated_file_path}")


   except FileNotFoundError:
       print("File not found. Please check the file path and try again.")
   except Exception as e:
       print(f"An error occurred: {e}")


if __name__ == '__main__':
    main()