Created
May 26, 2025 04:56
-
-
Save herronelou/68483020f26e185d39498fa1944538d5 to your computer and use it in GitHub Desktop.
Nuke Audio generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
set cut_paste_input [stack 0] | |
version 14.1 v5 | |
push $cut_paste_input | |
Expression { | |
channel0 {rgba.red -rgba.green rgba.blue} | |
name Expression5 | |
selected true | |
xpos 2349 | |
ypos -55 | |
addUserKnob {20 User} | |
addUserKnob {3 sampling l "Sampling Rate"} | |
sampling 44100 | |
addUserKnob {1 song l Song} | |
song "E4, D#4, E4, D#4, E4, B3, D4, C4, A3, (A3 C3), (A3 E2), (A3 A2), (A3 C3), \nE3, A3, B3, (B3 E2), (B3 G#2), (B3 E3), \nG#3, B3, C4, (C4 E2), (C4 A2), (C4 E3), \nE4, D#4, E4, D#4, E4, B3, D4, C4, A3, (A3 E2), (A3 A2), (A3 C3), \nE3, A3, B3, (B3 E2), (B3 A2), (B3 G#2), (B3 E3), \nC4, B3, A3, (A3 E2), (A3 A2), A3 ,\n\nE4, D#4, E4, D#4, E4, B3, D4, C4, A3,(A3 E2), (A3 A2), (A3 C3), \nE3, A3, B3, (B3 E2), (B3 G#2), (B3 E3),\nG#3, B3, C4,(C4 E2), E3,\nE4, D#4, E4, D#4, E4, B3, D4, C4, A3, (A3 E2), A2, C3, E3, A3, \n B3, (B3 E2), G#2, E3, C4, B3, A3, (A3 E2), A2, B3, C4, D4, E4, (E4 G2), (E4 C3), (E4 G3), F4, E4, \nD4, (D4 G2), (D4 B2), (D4 F3), E4, D4, C4, (C4 E2), (C4 A2), (C4 E3), D4, C4, B3, (B3 E2), E3, E3, E4, E3, E4, E4,, \nE4, D#4, E4, D#4, E4,B3, D4, C4, A3,(A3 E2), A2, C3, E3, A3, B3, (B3 E2), G#2, E3, G#3, B3, C4, (C4 E2), A2, E3,\nE4, D#4, E4, D#4, E4, B3, D4, C4, A3, (A3 E2), A2, C3, E3, A2, B3, (B3 E2), G#2, E3\n" | |
addUserKnob {22 generate l Generate T "import math\nimport re\n\n# --- Configuration ---\nA4_FREQ = 440.0 # Frequency of A4 note\n\n# Semitone values relative to C in an octave (C=0, C#=1, ..., B=11)\n# This map helps convert note names (like 'A', 'C') to a base semitone value.\nNOTE_TO_SEMITONE_MAP = \{\n 'C': 0, 'D': 2, 'E': 4, 'F': 5, 'G': 7, 'A': 9, 'B': 11\n\}\n# Reference for A4: Octave 4, Note 'A'\nA4_OCTAVE = 4\nA4_SEMITONE_OFFSET_IN_OCTAVE = NOTE_TO_SEMITONE_MAP\['A'] # Should be 9\n# Calculate the number of semitones A4 is from C0 (C in octave 0)\nNUM_SEMITONES_A4_FROM_C0 = A4_OCTAVE * 12 + A4_SEMITONE_OFFSET_IN_OCTAVE\n\n\ndef get_note_frequency(note_str: str) -> float:\n \"\"\"\n Calculates the frequency of a given musical note string.\n Args:\n note_str: The note string, e.g., \"A4\", \"C#3\", \"B#2\".\n Handles sharps (#). B# is treated as C of the next octave,\n E# is treated as F of the same octave.\n Returns:\n The frequency of the note in Hz.\n Raises:\n ValueError: If the note format is invalid.\n \"\"\"\n note_str = note_str.strip()\n # Regex for notes like C4, A#3. Octaves 0-8. Sharp is optional.\n # Example: \"A#3\" -> name='A', sharp_char='#', octave_str='3'\n # Example: \"C4\" -> name='C', sharp_char='', octave_str='4'\n match = re.fullmatch(r'(\[A-G])(\[#]?)(\[0-8])', note_str)\n if not match:\n raise ValueError(\n f\"Invalid note format: '\{note_str\}'. Expected format e.g., 'A4', 'C#3'.\"\n )\n\n name, sharp_char, octave_str = match.groups()\n octave = int(octave_str)\n\n semitone_offset = NOTE_TO_SEMITONE_MAP\[name]\n\n if sharp_char == '#':\n semitone_offset += 1\n \n # Normalize semitone offset and octave for cases like B# or E#\n # If semitone_offset becomes 12 (e.g., B#), it's C (0) of the next octave.\n # If semitone_offset becomes 5 due to E# (E=4 + #=1), it's F (5), no octave change needed unless it goes >=12.\n if semitone_offset >= 12:\n semitone_offset -= 12 # Wrap around to 0 (C)\n octave += 1\n \n # Calculate the total number of semitones from C0 for the current note\n num_semitones_current_note_from_C0 = octave * 12 + semitone_offset\n\n # Calculate the difference in semitones from A4\n semitone_diff_from_A4 = num_semitones_current_note_from_C0 - NUM_SEMITONES_A4_FROM_C0\n \n # Calculate frequency using the formula: f = f_ref * (2^(1/12))^n\n frequency = A4_FREQ * (2**(semitone_diff_from_A4 / 12.0))\n return frequency\n\n\ndef get_bin_for_frequency(frequency: float, fftsize: int, sampling_rate: float) -> int:\n \"\"\"\n Calculates the nearest FFT bin index for a given frequency.\n Args:\n frequency: The frequency in Hz.\n fftsize: The size of the FFT.\n sampling_rate: The sampling rate in Hz.\n Returns:\n The nearest FFT bin index.\n Raises:\n ValueError: If fftsize or sampling_rate are not positive.\n \"\"\"\n if fftsize <= 0:\n raise ValueError(\"FFT size must be positive.\")\n if sampling_rate <= 0:\n raise ValueError(\"Sampling rate must be positive.\")\n\n if frequency == 0: # Should generally not be the case for musical notes\n return 0\n \n # Frequency resolution: df = sampling_rate / fftsize\n # Bin index k for frequency f: f = k * df => k = f / df = f * fftsize / sampling_rate\n bin_index_float = frequency * fftsize / sampling_rate\n bin_index = round(bin_index_float)\n\n # Ensure bin_index is within the valid range \[0, fftsize - 1]\n # The FFT output has 'fftsize' bins, indexed 0 to fftsize-1.\n return max(0, min(int(bin_index), fftsize - 1))\n\n\ndef generate_expression(input_music_string: str, fftsize: int, sampling_rate: float) -> str:\n \"\"\"\n Generates the expression string from the input music notation.\n Args:\n input_music_string: The music string, e.g., \"A4, 0, (C4 E4 G4), B#2\".\n fftsize: The FFT size.\n sampling_rate: The sampling rate.\n Returns:\n The generated expression string.\n \"\"\"\n time_events_raw = input_music_string.split(',')\n time_sample_index = 0 # 'y' in the output expression\n conditions = \[]\n\n for time_event_str_raw in time_events_raw:\n time_event_str = time_event_str_raw.strip()\n \n # If the part after split is empty (e.g. \"A1,\"), it's an empty time step.\n # It will not generate notes, but time_sample_index will still advance.\n if not time_event_str:\n time_sample_index += 1\n continue\n\n notes_for_current_sample = \[]\n\n if time_event_str == '0':\n # Silence for this time sample, no notes to add.\n pass\n elif time_event_str.startswith('(') and time_event_str.endswith(')'):\n # Chord: (Note1 Note2 Note3)\n chord_content = time_event_str\[1:-1].strip()\n if chord_content: # Ensure not an empty chord \"()\"\n # Notes within a chord are space-separated\n note_strs_in_chord = chord_content.split()\n for note_str in note_strs_in_chord:\n notes_for_current_sample.append(note_str.strip())\n else:\n # Single note\n notes_for_current_sample.append(time_event_str)\n\n for note_to_process in notes_for_current_sample:\n try:\n frequency = get_note_frequency(note_to_process)\n bin_index = get_bin_for_frequency(frequency, fftsize, sampling_rate)\n # Add condition: x == bin_index AND y == time_sample_index\n conditions.append(f\"x==\{bin_index\}&&y==\{time_sample_index\}\")\n except ValueError as e:\n # Print a warning and skip this invalid note.\n print(f\"Warning: Skipping invalid note '\{note_to_process\}' at time sample \{time_sample_index\}: \{e\}\")\n \n time_sample_index += 1 # Move to the next time sample\n\n # Join all conditions with OR operator\n return \"||\".join(conditions)\n\nnode = nuke.thisNode()\nsong = node\['song'].value()\nresult = generate_expression(song, (node.width()-1)*2, node\['sampling'].value())\nnode\['expr0'].setValue(result)" +STARTLINE} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment