Python to .repatch (Mutator)

This forum is for developers of Rack Extensions to discuss the RE SDK, share code, and offer tips to other developers.
User avatar
Billy+
Posts: 4162
Joined: 09 Dec 2016

03 Nov 2023

Enlightenspeed wrote:
29 Oct 2023
Hi Billy,

I was curious about this, so did some digging around for my own amusement. Turns out MIDI is an uglier beast than I originally imagined! I've written some code for reading it now (without using any Python imports) but in order to help you with your cause, and to turn it into repatch files, could you give me a small subset of the dictionary you are using?

I would only need about 20 entries worth, including the original header if it has one.

I'm going on hols to the sunshine in less than a week now, so I'll be unlikely to get much done with it until I'm back, thus a little patience may be required.

Cheers,
Brian
hey sorry for the delay, i've got stuff going on in the real world - is it ok to drop you a pm within the next few days?

i downloaded your code and have only really just had a few hours with it and have a few questions that i will respond to in the main forum asap.

User avatar
Billy+
Posts: 4162
Joined: 09 Dec 2016

04 Nov 2023

10000-midifiles.png
10000-midifiles.png (73.56 KiB) Viewed 7679 times

Code: Select all

import random                       # needed for random string
import os                           # needed for current_directory
try:
    from midiutil import MIDIFile   # needed to make midi files
except ImportError:
    print('missing midiutil')


'''
One possible way to add a check to make sure none of the random strings are identical is to use a set data structure in Python.
A set is a collection of unique elements that does not allow duplicates.
You can create an empty set and add each random string to it as you generate them.
If the length of the set is equal to the number of random strings, then there are no duplicates.
'''

def rando(count):
    aset = set()
    while len(aset) < (count):
        string16 = ''.join(random.choices('hmsr', k=16))
        aset.add(string16)

    return aset


def process_notes(notes, bpm, output, human):
    num_notes = len(notes)
    duration_16th_note = (60 / bpm) / 2
    track = 0
    time = 0
    channel = 9
    pitch = 60
    volume = 0

    notice = 'random rhythm file'

    midi_file = MIDIFile(1)
    midi_file.addTrackName(track, time, notes)
    midi_file.addTempo(track, time, bpm)
    midi_file.addCopyright(track, time, notice)

    current_directory = os.getcwd()
    saveas = f'{current_directory}\{output}'

    print(f'Sequencing : Pattern [{notes}] saving as [{output}] in [{current_directory}]')

    for i in range(num_notes):
        note = notes[i]
        if note == 'h':
            volume = 100 
            if human: volume = 100 + random.randint(-7, 7)
        elif note == 'm':
            volume = 75
            if human: volume = 75 + random.randint(-10, 10)
        elif note == 's':
            volume = 50
            if human: volume = 50 + random.randint(-5, 5)
        elif note == 'r':
            time += duration_16th_note
            if debug:
                print(f"Note {i+1:02}: {note}, Onset: {time}, Duration: {duration_16th_note}")
            continue

        midi_file.addNote(track, channel, pitch, time, duration=duration_16th_note, volume=volume )
        time += duration_16th_note
        if debug:
            print(f"Note {i+1:02}: {note}, Volume: {volume}, Onset: {time}, Duration: {duration_16th_note}")

    with open(saveas, "wb") as output_file:
        midi_file.writeFile(output_file)


if __name__ == '__main__':
    global debug
    # setting debug to True will print more info.
    debug = False
    # this is set to 0 by default to stop you running without understanding ;)
    # Maximum number of files in a single FAT32 folder: 65,534
    amount = 0
    if amount == 0:
        print('read me first..')
    else:
        strings = rando(amount)
        for string in strings:
            process_notes(string, 120, (string)+'.mid', human=True)



ok so I've knocked together a script that will produce as many midi files "within reason" as you want,
I tested it on 10000 and without playing all the files it seems to function as expected.

working with these is much simpler than wild midi files (I got the Clean MIDI subset from https://colinraffel.com/projects/lmd/).

User avatar
Enlightenspeed
RE Developer
Posts: 1106
Joined: 03 Jan 2019

10 Nov 2023

Billy+ wrote:
03 Nov 2023
hey sorry for the delay, i've got stuff going on in the real world - is it ok to drop you a pm within the next few days?

i downloaded your code and have only really just had a few hours with it and have a few questions that i will respond to in the main forum asap.
Hi Billy,

I have now returned from the sunshine!!! I also have a nasty wee bugger of a cold, so am trying to preserve energy where I can over the next few.

Fire away with the questions :)

Cheers,
B

User avatar
Billy+
Posts: 4162
Joined: 09 Dec 2016

22 Dec 2023

Hi all sorry for the MIA things have been tricky on my end - anyways. the generator is working well however I'm not ready to release yet, I've also updated the midi generator - but reason won't use all the gm instruments I've assigned to the midi but that's a non issue really as its about the patterns (and you can / should be changing them to something you want.)

Code: Select all

PS C:\billy> python 16.py --list_instruments
Available Instruments:
sticks
side stick
hand clap
tambourine
cowbell
maracas
shaker
castanets
toms 1
toms 2
bongo
scratch
PS C:\billy> python 16.py --help

usage: 16.py [-h] [--instrument] [--list_instruments] [--bpm [60-200]] [--amount] [--max_adjacent] [--ignore_char] [--human] [--debug]

creates midi files from a random pattern of 16ths

optional arguments:
  -h, --help          show this help message and exit
  --instrument        instrument to use, make sure to use quotes around an instrument containing multiply words for example "toms 1" (default: bongo)
  --list_instruments  list available instruments (default: False)
  --bpm [60-200]      beats per minute (60 - 200) (default: 120)
  --amount            number of files to create (maximum is 10000) (default: 1)
  --max_adjacent      maximum adjacent repeated character (default: 1)
  --ignore_char       character to ignore when adjacent to its self (default: None)
  --human             to humanize or not (default: True)
  --debug             print extra debug info, skips midi file creation (default: False)
PS C:\billy>
any ways I had planed to upload a 1000 PM Patches but the 1.1Mb zip is causing a http error so for now its just an update and a quick Q to the devs.
buddard you about ?
buddard wrote:
26 Sep 2023
I noticed that this field seems to always be empty ?
<Value property="accent_lane_1" type="string" />
is there any chance someone can explain what if anything I could do with this ?

ok the link will only last 30 days... PM Xmas Pack 1000 repatch files for Pattern Mutator https://ufile.io/r7cp3dnq
if anyone knows of a better site feel free to post.

Have a great holiday everyone ;)

User avatar
buddard
RE Developer
Posts: 1245
Joined: 17 Jan 2015
Location: Stockholm
Contact:

23 Dec 2023

Billy+ wrote:
22 Dec 2023
I noticed that this field seems to always be empty ?
<Value property="accent_lane_1" type="string" />
is there any chance someone can explain what if anything I could do with this ?
The accent lane gets data added to it when you mutate a pattern with the knob "Velocity (Accent) Mutate Amount" set above 0.
What is on that lane will be sent to the Accent CV output as trigger pulses.

This is described in the manual, by the way -- Always a good starting point when you're reverse engineering stuff. ;-)

User avatar
Enlightenspeed
RE Developer
Posts: 1106
Joined: 03 Jan 2019

23 Dec 2023

Billy+ wrote:
22 Dec 2023
I noticed that this field seems to always be empty ?
<Value property="accent_lane_1" type="string" />
This is the bit from my generator script to have a little look at

if accentsOn == True:
accentString = "01"
for each in range(0 , globalLength):
if random.randint(1, 100) > accentProb:
accentString = accentString + "FF"
for each in range(0 , maxSteps - globalLength):
accentString = accentString + "01"

The short of it is that it's a true or false value you want, and the numeric used is either 01 or 255.

Oh, and happy holidays to all :)
B

User avatar
Billy+
Posts: 4162
Joined: 09 Dec 2016

23 Dec 2023

thanks guys...

by the way I've also been working on a Dual Arp Random Generator as well
DualArpPatternGen.png
DualArpPatternGen.png (9.03 KiB) Viewed 4273 times

MODS : - what's the zip file size limit ?
is there any chance you can reattach the zip I posted rather than leaving it on the time limited site ?

Post Reply
  • Information
  • Who is online

    Users browsing this forum: No registered users and 6 guests