Python to .repatch (Mutator)
-
- Posts: 4217
- Joined: 09 Dec 2016
Ok so i've been busy creating a few python scripts that produce midi files and i would really love a simple way to process / convert them into pattern mutator repatch files so i thought i would reach out to any devs that might be able to assist or already have a script?
any help would be appreciated.
at the moment i'm guessing that the pattern_data_1 is a hex string but i could be well off, and would much rather not reinvent a wheel that might already be in someone's toolbox
any help would be appreciated.
at the moment i'm guessing that the pattern_data_1 is a hex string but i could be well off, and would much rather not reinvent a wheel that might already be in someone's toolbox
-
- Posts: 4217
- Joined: 09 Dec 2016
so this is what am currently doing
all I'm missing is a midi2repatch script...
if anyone is interested in the scripts let me know, and if anyone can provide some info on the next phase (midi2repatch.py) that would be great.
Code: Select all
c:\Billy>python random_rhythm_generator.py --help
usage: random_rhythm_generator.py [-h] [--charset CHARSET] [--rotate-left] [--rotate-right]
optional arguments:
-h, --help show this help message and exit
--charset CHARSET character set to use for generating random rhythm
--rotate-left rotate the string to the left
--rotate-right rotate the string to the right
c:\Billy>python random_rhythm_generator.py
Todays Random Rhythm is : rsrhhhhmsssshrhh
c:\Billy>python musicrhythm.py --help
usage: musicrhythm.py [-h] [--bpm BPM] [--output OUTPUT] [--human] N
Generate a MIDI file from a list of notes with volume and rest.
positional arguments:
N a string of notes and reset (h=Hard, m=Medium, s=Soft r=Rest)
optional arguments:
-h, --help show this help message and exit
--bpm BPM the tempo in beats per minute (default: 120)
--output OUTPUT the name of the output MIDI file (default: output.mid)
--human adds slight random volume to the notes.
c:\Billy>python musicrhythm.py --bpm 90 --human rsrhhhhmsssshrhh
Sequence of notes:
Note 1: r, Onset: 0.3333333333333333, Duration: 0.3333333333333333
Note 2: s, Volume: 55, Onset: 0.6666666666666666, Duration: 0.3333333333333333
Note 3: r, Onset: 1.0, Duration: 0.3333333333333333
Note 4: h, Volume: 93, Onset: 1.3333333333333333, Duration: 0.3333333333333333
Note 5: h, Volume: 102, Onset: 1.6666666666666665, Duration: 0.3333333333333333
Note 6: h, Volume: 101, Onset: 1.9999999999999998, Duration: 0.3333333333333333
Note 7: h, Volume: 95, Onset: 2.333333333333333, Duration: 0.3333333333333333
Note 8: m, Volume: 78, Onset: 2.6666666666666665, Duration: 0.3333333333333333
Note 9: s, Volume: 49, Onset: 3.0, Duration: 0.3333333333333333
Note 10: s, Volume: 47, Onset: 3.3333333333333335, Duration: 0.3333333333333333
Note 11: s, Volume: 46, Onset: 3.666666666666667, Duration: 0.3333333333333333
Note 12: s, Volume: 53, Onset: 4.0, Duration: 0.3333333333333333
Note 13: h, Volume: 94, Onset: 4.333333333333333, Duration: 0.3333333333333333
Note 14: r, Onset: 4.666666666666666, Duration: 0.3333333333333333
Note 15: h, Volume: 94, Onset: 4.999999999999999, Duration: 0.3333333333333333
Note 16: h, Volume: 97, Onset: 5.333333333333332, Duration: 0.3333333333333333
midi files saved as : output.mid
c:\Billy>python random_rhythm_generator.py --rotate-left
Todays Random Rhythm is : rsmrmrsrrsrrshss
Rotation 1: smrmrsrrsrrshssr
Rotation 2: mrmrsrrsrrshssrs
Rotation 3: rmrsrrsrrshssrsm
Rotation 4: mrsrrsrrshssrsmr
Rotation 5: rsrrsrrshssrsmrm
Rotation 6: srrsrrshssrsmrmr
Rotation 7: rrsrrshssrsmrmrs
Rotation 8: rsrrshssrsmrmrsr
Rotation 9: srrshssrsmrmrsrr
Rotation 10: rrshssrsmrmrsrrs
Rotation 11: rshssrsmrmrsrrsr
Rotation 12: shssrsmrmrsrrsrr
Rotation 13: hssrsmrmrsrrsrrs
Rotation 14: ssrsmrmrsrrsrrsh
Rotation 15: srsmrmrsrrsrrshs
if anyone is interested in the scripts let me know, and if anyone can provide some info on the next phase (midi2repatch.py) that would be great.
-
- Posts: 4217
- Joined: 09 Dec 2016
ok so i've made some progress today but there are a few values i'm still not sure of and i know the props programmer love to have some fun so i'm wondering if maybe this value is just for giggles?
The 8380 twin flame symbolism is the act of satisfaction after you have met all your goals and dreams.
Code: Select all
<Value property="pattern_data_1" type="string" >
020110013D67028380023D5E028380033D63028380043D35028380053D6A028380063D54028380073D36028380083D69028380093D300283800A3D2E0283800B3D610283800C3D320283800D3D670283800E3D340283800F3D43028380
</Value>
Code: Select all
[Version?]0201 10 [16]=Steps
position | pitch+1 | velocity+1 | giggles
[1] 01 3D [103] 67 02 8380
[2] 02 3D [ 94] 5E 02 8380
[3] 03 3D [ 99] 63 02 8380
[4] 04 3D [ 53] 35 02 8380
[5] 05 3D [106] 6A 02 8380
[6] 06 3D [ 84] 54 02 8380
[7] 07 3D [ 54] 36 02 8380
[8] 08 3D [105] 69 02 8380
[9] 09 3D [ 48] 30 02 8380
[10] 0A 3D [ 46] 2E 02 8380
[11] 0B 3D [ 97] 61 02 8380
[12] 0C 3D [ 50] 32 02 8380
[13] 0D 3D [103] 67 02 8380
[14] 0E 3D [ 52] 34 02 8380
[15] 0F 3D [ 67] 43 02 8380
-
- Posts: 4217
- Joined: 09 Dec 2016
so i'm not 100% happy with the script but it can take a midi file and generate the string needed to make a PM.repatch file
it defo needs some more work, but I can basically generate files from my daily random rhythm generator
it defo needs some more work, but I can basically generate files from my daily random rhythm generator
You do not have the required permissions to view the files attached to this post.
-
- Posts: 4217
- Joined: 09 Dec 2016
took a bit more time on this today and i'm getting closer
I'm sure there's a simple solution but I've only just started with python - guess those that real know don't want to share so i will just keep plodding along
I'm sure there's a simple solution but I've only just started with python - guess those that real know don't want to share so i will just keep plodding along
You do not have the required permissions to view the files attached to this post.
-
- Posts: 4217
- Joined: 09 Dec 2016
so I finally got round to generating some more data for giggles - and it's no joke.....
Bar-1/128th.mid
Note 1 bar, time: 2.0
Note 1/2, time: 1.0
Note 1/4, time: 0.5
Note 1/8, time: 0.25
Note 1/16, time: 0.125
Note 1/32, time: 0.0625
Note 1/64, time: 0.03125
Note 1/128, time: 0.015625
decided this was enough data to play with for now - and time is an issue for me "would defo like some help"......
thankfully I'm only working in 16th so my scripts works, but it would be nice to extend / enhance this to work with other midi files.....
Bar-1/128th.mid
Note 1 bar, time: 2.0
Note 1/2, time: 1.0
Note 1/4, time: 0.5
Note 1/8, time: 0.25
Note 1/16, time: 0.125
Note 1/32, time: 0.0625
Note 1/64, time: 0.03125
Note 1/128, time: 0.015625
decided this was enough data to play with for now - and time is an issue for me "would defo like some help"......
thankfully I'm only working in 16th so my scripts works, but it would be nice to extend / enhance this to work with other midi files.....
-
- RE Developer
- Posts: 1111
- Joined: 03 Jan 2019
As a first step, you should ask RS to give you the schema for the data strings, and permission to discuss the schema publicly.
Without that, it's difficult for other devs to participate; any of us could do it easily enough, but it involves a level of reverse engineering, which is legally dubious - not that it would be any real threat of course, this stuff is far too basic.
The moral issue is still there, however, so if you can get RS to publicly agree to this first, then other RE devs can help.
-
- Competition Winner
- Posts: 4060
- Joined: 16 Jan 2015
And I'll just add that they're all a lot more open than you might think!
-
- Posts: 4217
- Joined: 09 Dec 2016
ah!Enlightenspeed wrote: ↑22 Sep 2023As a first step, you should ask RS to give you the schema for the data strings, and permission to discuss the schema publicly.
Without that, it's difficult for other devs to participate; any of us could do it easily enough, but it involves a level of reverse engineering, which is legally dubious - not that it would be any real threat of course, this stuff is far too basic.
The moral issue is still there, however, so if you can get RS to publicly agree to this first, then other RE devs can help.
hadn't really considered that......
Sorry Reason Studios, if you see this thread would you possibly chip in and advise on any concerns
if you would rather the info be removed please say so.
if however you're cool with it please give the go ahead
again sorry, and if mods want to remove images / code please feel free to
-
- RE Developer
- Posts: 1111
- Joined: 03 Jan 2019
I’d be surprised if they aren’t cool with it, but as a rule ask first. The patches are in plain text, not encrypted, so it’s easy to do this kind of thing, and this is probably intended by RS.Billy+ wrote: ↑22 Sep 2023
ah!
hadn't really considered that......
Sorry Reason Studios, if you see this thread would you possibly chip in and advise on any concerns
if you would rather the info be removed please say so.
if however you're cool with it please give the go ahead
again sorry, and if mods want to remove images / code please feel free to
In the spirit of sharing, if they make the schema available, I’ll write the script for you, and make it public
Cheers,
Brian
-
- Posts: 4217
- Joined: 09 Dec 2016
thanks manEnlightenspeed wrote: ↑22 Sep 2023I’d be surprised if they aren’t cool with it, but as a rule ask first. The patches are in plain text, not encrypted, so it’s easy to do this kind of thing, and this is probably intended by RS.Billy+ wrote: ↑22 Sep 2023
ah!
hadn't really considered that......
Sorry Reason Studios, if you see this thread would you possibly chip in and advise on any concerns
if you would rather the info be removed please say so.
if however you're cool with it please give the go ahead
again sorry, and if mods want to remove images / code please feel free to
In the spirit of sharing, if they make the schema available, I’ll write the script for you, and make it public
Cheers,
Brian
and seriously i didn't even think about the protentional issue, i guess i just got a bit of tunnel vision - i've been hitting a bit of a brick wall for a few months musically speaking and started out with thinking maybe some random rhythms might inspire something and it spiraled.
if i could remove the current insight i would but i can't
thanks for the intervention
-
- Posts: 4217
- Joined: 09 Dec 2016
i have updated the initial script (random_rhythm_generator.py) and im happy to share it...
example usage :-
this is the actual code below for anyone interested :- save as: random_rhythm_generator.py
example usage :-
Code: Select all
c:\Billy>python random_rhythm_generator.py --help
usage: random_rhythm_generator.py [-h] [--charset CHARSET] [--rotate-left] [--rotate-right] [--limiting-factor LIMITING_FACTOR] [--max-repeating MAX_REPEATING]
optional arguments:
-h, --help show this help message and exit
--charset CHARSET character set to use for generating random rhythm
--rotate-left rotate the string to the left
--rotate-right rotate the string to the right
--limiting-factor LIMITING_FACTOR
specify an individual character as a parameter to be the limiting factor
--max-repeating MAX_REPEATING
specify the maximum number of times a character can be repeated
c:\Billy>python random_rhythm_generator.py
Todays Random Rhythm is : smsshmmrrmmshsrh
c:\Billy>python random_rhythm_generator.py --rotate-right --limiting-factor h3 --max-repeating 2
Todays Random Rhythm is : shmhsmrhmrmmsrss
Rotation 01: sshmhsmrhmrmmsrs
Rotation 02: ssshmhsmrhmrmmsr
Rotation 03: rssshmhsmrhmrmms
Rotation 04: srssshmhsmrhmrmm
Rotation 05: msrssshmhsmrhmrm
Rotation 06: mmsrssshmhsmrhmr
Rotation 07: rmmsrssshmhsmrhm
Rotation 08: mrmmsrssshmhsmrh
Rotation 09: hmrmmsrssshmhsmr
Rotation 10: rhmrmmsrssshmhsm
Rotation 11: mrhmrmmsrssshmhs
Rotation 12: smrhmrmmsrssshmh
Rotation 13: hsmrhmrmmsrssshm
Rotation 14: mhsmrhmrmmsrsssh
Rotation 15: hmhsmrhmrmmsrsss
Code: Select all
import argparse
import random
import string
def rotate_string(string, rotation):
return string[rotation:] + string[:rotation]
def modify_string(string, limiting_factor, max_repeating):
limiting_character, limiting_count = limiting_factor[0], int(limiting_factor[1:])
limiting_factor_count = string.count(limiting_character)
if limiting_factor_count > limiting_count:
new_random_string = string.replace(limiting_character, limiting_character * limiting_count)
else:
new_random_string = string
if max_repeating is not None:
new_random_string = enforce_max_repeating(new_random_string, max_repeating)
return new_random_string
def enforce_max_repeating(string, max_repeating):
for char in set(string):
if string.count(char) > max_repeating:
string = string.replace(char * max_repeating, char * max_repeating + char)
return string
def generate_random_string(charset, length):
return ''.join(random.choices(charset, k=length))
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--charset', type=str, default='hmsr', help='character set to use for generating random rhythm')
parser.add_argument('--rotate-left', action='store_true', help='rotate the string to the left')
parser.add_argument('--rotate-right', action='store_true', help='rotate the string to the right')
parser.add_argument('--limiting-factor', type=str, default=None, help='specify an individual character as a parameter to be the limiting factor')
parser.add_argument('--max-repeating', type=int, default=None, help='specify the maximum number of times a character can be repeated')
args = parser.parse_args()
random_string = generate_random_string(args.charset, 16)
if args.rotate_left:
for i in range(len(random_string)):
rotated_string = rotate_string(random_string, i)
if i == 0:
print(f"Todays Random Rhythm is : {random_string} ")
elif i > 0:
print(f"Rotation {i:02}: {rotated_string}")
elif args.rotate_right:
for i in range(len(random_string)):
rotated_string = rotate_string(random_string, -i)
if i == 0:
print(f"Todays Random Rhythm is : {random_string} ")
elif i > 0:
print(f"Rotation {i:02}: {rotated_string}")
elif args.limiting_factor is not None:
new_random_string = modify_string(random_string, args.limiting_factor, args.max_repeating)
print(f"Todays Random Rhythm is : {new_random_string} ")
else:
print(f"Todays Random Rhythm is : {random_string} ")
if __name__ == "__main__":
main()
-
- Competition Winner
- Posts: 4060
- Joined: 16 Jan 2015
Funnily enough, a few months ago I'd made this track using instruments (as I normally do). But I had a drum kit loaded with a good 64+ chord and note stabs.
I was surprised by how much they added to my track.
I think you might be onto something.
-
- Posts: 4217
- Joined: 09 Dec 2016
yeah its funny how just getting something you wouldn't normally do can inspire.
I also have / had a brute force dictionary of numbers that I randomly grab a line from and use them to create chords etc.
this project just created a rhythm that I wouldn't normally create, I stuck with 16th that are either Rest Hard Medium or Soft hits.
oh I just realized that the script it's the best version
, I was trying to extend it but failed..it's got bugs
-
- Posts: 4217
- Joined: 09 Dec 2016
I also made another script that walks through a folder and subfolder playing wav mid files and listing patches etc, so I don't get distracted by shinny things and instead just listen to what's available..... that way I just sit back listen and wait for something to jump out at me
this is basically its output on the console, and anything that can play gets played
c:\Billy>python playkit.py c:\DeepHouseCollection\Club_Essential_Series_-_Deep_House_Kits_Vol4_product_pack
FOLDER : EAM_Club_Series_Deep_House_Kits_Vol_4 [FILES 2]
FOLDER : KIT 01 126 A# [FILES 1]
* Playing FULL KIT 01 126 A#.wav
FOLDER : LOOPS [FILES 8]
* Playing BASS.wav
* Playing CLAP.wav
* Playing KICK.wav
* Playing PERC LOOP.wav
* Playing SHAKER LOOP 2.wav
* Playing SHAKER LOOP.wav
* Playing TRUMPET.wav
* Playing VOCAL.wav
FOLDER : MIDI [FILES 2]
* Playing BASS.mid
* Playing TRUMPET.mid
FOLDER : ONE SHOTS [FILES 10]
* Playing BASS SHOT A#.wav
* Playing CLAP.wav
* Playing CRASH.wav
* Playing KICK.wav
* Playing PERC 01.wav
* Playing SHAKER 01.wav
* Playing SHAKER 02.wav
* Playing TRUMPET C#.wav
* Playing VOCAL SHOT.wav
* Playing VOX.wav
FOLDER : PRESETS [FILES 1]
BASS - SPIRE.spf is a Spire Patch File.
FOLDER : STEMS [FILES 10]
* Playing BASS.wav
* Playing CLAP.wav
* Playing CRASH.wav
* Playing KICK.wav
* Playing PERC LOOP.wav
* Playing SHAKER LOOP 01.wav
* Playing SHAKER LOOP 20.wav
* Playing TRUMPET.wav
* Playing VOCAL SHOT.wav
* Playing VOX.wav
FOLDER : KIT 02 126 G [FILES 1]
* Playing FULL KIT G 126.wav
this is basically its output on the console, and anything that can play gets played
c:\Billy>python playkit.py c:\DeepHouseCollection\Club_Essential_Series_-_Deep_House_Kits_Vol4_product_pack
FOLDER : EAM_Club_Series_Deep_House_Kits_Vol_4 [FILES 2]
FOLDER : KIT 01 126 A# [FILES 1]
* Playing FULL KIT 01 126 A#.wav
FOLDER : LOOPS [FILES 8]
* Playing BASS.wav
* Playing CLAP.wav
* Playing KICK.wav
* Playing PERC LOOP.wav
* Playing SHAKER LOOP 2.wav
* Playing SHAKER LOOP.wav
* Playing TRUMPET.wav
* Playing VOCAL.wav
FOLDER : MIDI [FILES 2]
* Playing BASS.mid
* Playing TRUMPET.mid
FOLDER : ONE SHOTS [FILES 10]
* Playing BASS SHOT A#.wav
* Playing CLAP.wav
* Playing CRASH.wav
* Playing KICK.wav
* Playing PERC 01.wav
* Playing SHAKER 01.wav
* Playing SHAKER 02.wav
* Playing TRUMPET C#.wav
* Playing VOCAL SHOT.wav
* Playing VOX.wav
FOLDER : PRESETS [FILES 1]
BASS - SPIRE.spf is a Spire Patch File.
FOLDER : STEMS [FILES 10]
* Playing BASS.wav
* Playing CLAP.wav
* Playing CRASH.wav
* Playing KICK.wav
* Playing PERC LOOP.wav
* Playing SHAKER LOOP 01.wav
* Playing SHAKER LOOP 20.wav
* Playing TRUMPET.wav
* Playing VOCAL SHOT.wav
* Playing VOX.wav
FOLDER : KIT 02 126 G [FILES 1]
* Playing FULL KIT G 126.wav
-
- RE Developer
- Posts: 1264
- Joined: 17 Jan 2015
- Location: Stockholm
I had overlooked this topic, but thanks to a heads up from Avasopht over at the official dev forum I came here.
Here is the pattern format for Mutator:
Pattern note data is packed into string properties in the RE "motherboard".
And as you have already guessed, string properties are stored in hex format in the .repatch file.
We can't use any 0 values in the data since it will terminate the string, so most values will range from 1 - 255 (01 - FF).
The first byte is the pattern data version, which is currently 2. (or "02" in hex). Don't change this
The next two bytes represent the number of notes in the pattern, where the first byte is the most significant byte.
So if your pattern has 16 notes, the string sequence will be "0111" (remember that each byte must be offset by 1...)
A string property can hold a maximum of 2048 bytes, and each note uses 6 bytes, so the maximum number of notes per pattern supported by Mutator is 340.
Next you have the data for each note in the pattern, and that is laid out as follows, per byte:
- Note start (in steps, 1 - 128)
- Note number (1 - 128, MIDI note number offset by 1)
- Note velocity (1 - 128, i e offset by 1)
- Note length (in steps, offset by 1)
- Note flags (see below)
- Length scale factor (in 1/254ths, offset by 1)
The note flags value is 128 + (32 if the note is muted) + (16 if the note is tied) + the octave offset (0 - 6 corresponds to -3 - +3. the normal value is 3).
For a plain unmodified note, the flag value is 128 + 3 = 131, or "83" in hex.
The per-note mute and octave offset are not normally used by Mutator, they were inherited from Sequences. But the playback engine supports them.
I might have made mistakes in the description or explained something poorly, so feel free to ask questions!
Here is the pattern format for Mutator:
Pattern note data is packed into string properties in the RE "motherboard".
And as you have already guessed, string properties are stored in hex format in the .repatch file.
We can't use any 0 values in the data since it will terminate the string, so most values will range from 1 - 255 (01 - FF).
The first byte is the pattern data version, which is currently 2. (or "02" in hex). Don't change this
The next two bytes represent the number of notes in the pattern, where the first byte is the most significant byte.
So if your pattern has 16 notes, the string sequence will be "0111" (remember that each byte must be offset by 1...)
A string property can hold a maximum of 2048 bytes, and each note uses 6 bytes, so the maximum number of notes per pattern supported by Mutator is 340.
Next you have the data for each note in the pattern, and that is laid out as follows, per byte:
- Note start (in steps, 1 - 128)
- Note number (1 - 128, MIDI note number offset by 1)
- Note velocity (1 - 128, i e offset by 1)
- Note length (in steps, offset by 1)
- Note flags (see below)
- Length scale factor (in 1/254ths, offset by 1)
The note flags value is 128 + (32 if the note is muted) + (16 if the note is tied) + the octave offset (0 - 6 corresponds to -3 - +3. the normal value is 3).
For a plain unmodified note, the flag value is 128 + 3 = 131, or "83" in hex.
The per-note mute and octave offset are not normally used by Mutator, they were inherited from Sequences. But the playback engine supports them.
I might have made mistakes in the description or explained something poorly, so feel free to ask questions!
-
- Posts: 4217
- Joined: 09 Dec 2016
so I thought I would start with decoding a value first
does that look right to others ?
I'm still having difficulty getting my head around it all, got stuff going on that's keeping me away from the screen.
as things stand my initial script works fine when working with my random rhythm generator script as I'm only working in 1/16th for 16 steps, but being able to dump any reasonable size midi into PM would just be cool.
that's the output from the above.
Code: Select all
def mutator_schema(hex_string):
# int(value, base [16=hex])
version = int(hex_string[:2], 16)
total_notes = int(hex_string[4:6], 16)
note_data = []
for i in range(len(hex_string) // 12):
# i think the about might be wrong ?
step = int(hex_string[6 + i * 12:8 + i * 12], 16)
pitch = int(hex_string[8 + i * 12:10 + i * 12], 16) - 1
velocity = int(hex_string[10 + i * 12:12 + i * 12], 16) - 1
lengthinsteps = int(hex_string[12 + i * 12:14 + i * 12], 16) - 1
# these are possibly less needed when converting from midi
flags = int(hex_string[14 + i * 12:16 + i * 12], 16)
mute = bool(flags & 32)
tied = bool(flags & 16)
octave_offset = flags & 7 - 3
note_data.append((step, pitch, velocity, lengthinsteps, mute, tied, octave_offset))
return version, total_notes, note_data
pattern_data_1 = "020110013D67028380023D5E028380033D63028380043D35028380053D6A028380063D54028380073D36028380083D69028380093D300283800A3D2E0283800B3D610283800C3D320283800D3D670283800E3D340283800F3D43028380"
version, total_notes, note_data = mutator_schema(pattern_data_1)
print(f'{version} {total_notes}')
for note in note_data:
step, pitch, velocity, lengthinsteps, mute, tied, octave_offset = note
print(f"Note: pos={step:02}, pitch={pitch}, velocity={velocity:03}, length={lengthinsteps}, mute={mute}, tied={tied}, octave_offset={octave_offset}")
I'm still having difficulty getting my head around it all, got stuff going on that's keeping me away from the screen.
as things stand my initial script works fine when working with my random rhythm generator script as I'm only working in 1/16th for 16 steps, but being able to dump any reasonable size midi into PM would just be cool.
that's the output from the above.
Code: Select all
2 16
Note: pos=01, pitch=60, velocity=102, length=1, mute=False, tied=False, octave_offset=0
Note: pos=02, pitch=60, velocity=093, length=1, mute=False, tied=False, octave_offset=0
Note: pos=03, pitch=60, velocity=098, length=1, mute=False, tied=False, octave_offset=0
Note: pos=04, pitch=60, velocity=052, length=1, mute=False, tied=False, octave_offset=0
Note: pos=05, pitch=60, velocity=105, length=1, mute=False, tied=False, octave_offset=0
Note: pos=06, pitch=60, velocity=083, length=1, mute=False, tied=False, octave_offset=0
Note: pos=07, pitch=60, velocity=053, length=1, mute=False, tied=False, octave_offset=0
Note: pos=08, pitch=60, velocity=104, length=1, mute=False, tied=False, octave_offset=0
Note: pos=09, pitch=60, velocity=047, length=1, mute=False, tied=False, octave_offset=0
Note: pos=10, pitch=60, velocity=045, length=1, mute=False, tied=False, octave_offset=0
Note: pos=11, pitch=60, velocity=096, length=1, mute=False, tied=False, octave_offset=0
Note: pos=12, pitch=60, velocity=049, length=1, mute=False, tied=False, octave_offset=0
Note: pos=13, pitch=60, velocity=102, length=1, mute=False, tied=False, octave_offset=0
Note: pos=14, pitch=60, velocity=051, length=1, mute=False, tied=False, octave_offset=0
Note: pos=15, pitch=60, velocity=066, length=1, mute=False, tied=False, octave_offset=0
PS C:\Billy>
-
- Posts: 4217
- Joined: 09 Dec 2016
i've also generated a midi file and repatch for whole note down to a 128th
which returns this data
which tells me I'm going to need to do more with the pos (step) value and the length as everything below a 16 is always 1. anyway that's me finished for the next 20 ish hours.. maybe someone can shed some light onto the topic...
Code: Select all
pattern_data_1 = "020108013D65118380113D65098380193D650583801D3D650383801F3D65028380203D65028380213D65028380"
Code: Select all
2 7
Note: pos=01, pitch=60, velocity=100, length=16, mute=False, tied=False, octave_offset=0
Note: pos=17, pitch=60, velocity=100, length=8, mute=False, tied=False, octave_offset=0
Note: pos=25, pitch=60, velocity=100, length=4, mute=False, tied=False, octave_offset=0
Note: pos=29, pitch=60, velocity=100, length=2, mute=False, tied=False, octave_offset=0
Note: pos=31, pitch=60, velocity=100, length=1, mute=False, tied=False, octave_offset=0
Note: pos=32, pitch=60, velocity=100, length=1, mute=False, tied=False, octave_offset=0
Note: pos=33, pitch=60, velocity=100, length=1, mute=False, tied=False, octave_offset=0
-
- Posts: 4217
- Joined: 09 Dec 2016
for what its worth, this is my current progress with generating the string but its incomplete, all I've added is the info from buddard
BE WARNED
it might even be completely broke as i might have added stuff before i got advised to hold on for permission
BE WARNED
Code: Select all
import sys
from mido import MidiFile
'''
Here is the pattern format for Mutator:
Pattern note data is packed into string properties in the RE "motherboard".
And as you have already guessed, string properties are stored in hex format in the .repatch file.
We can't use any 0 values in the data since it will terminate the string, so most values will range from 1 - 255 (01 - FF).
The first byte is the pattern data version, which is currently 2. (or "02" in hex). Don't change this ;)
The next two bytes represent the number of notes in the pattern, where the first byte is the most significant byte.
So if your pattern has 16 notes, the string sequence will be "0111" (remember that each byte must be offset by 1...)
A string property can hold a maximum of 2048 bytes, and each note uses 6 bytes,
so the maximum number of notes per pattern supported by Mutator is 340.
Next you have the data for each note in the pattern, and that is laid out as follows, per byte:
- Note start (in steps, 1 - 128)
- Note number (1 - 128, MIDI note number offset by 1)
- Note velocity (1 - 128, i e offset by 1)
- Note length (in steps, offset by 1)
- Note flags (see below)
- Length scale factor (in 1/254ths, offset by 1)
The note flags value is 128 + (32 if the note is muted) + (16 if the note is tied) + the octave offset (0 - 6 corresponds to -3 - +3.
the normal value is 3).
For a plain unmodified note, the flag value is 128 + 3 = 131, or "83" in hex.
The per-note mute and octave offset are not normally used by Mutator, they were inherited from Sequences.
But the playback engine supports them.
'''
ppqn = 0
whole_note_duration = 0
# could just be a version number, or might be more...
header = '0201'
# hex the step value!, update to count the line later....
steps = 16
# this defo has more meaning but for now it's all i got ;)
# giggle is no joke, it must have something to do with note lenght / off etc. (dump some midi from the repatch files you have ;))
giggle = '028380'
# first part of the string seams to be this ? on the small data set i have tested...
pattern_data_1 = f'{header}{steps:02X}'
def warningnotice():
try:
print("This script is almost certainty broken and should only be used if you accept responsibility")
agree = input("Do you argee (y/n): ")
if agree.lower() == "n":
print("Exiting the program...")
sys.exit(0)
except Exception as e:
print(f"An error occurred: {e}")
sys.exit(1)
def calculate_standard_notation(note_duration):
# Calculate the length of the note in standard notation
standard_notation = ""
if note_duration == whole_note_duration:
standard_notation = "Whole Note"
elif note_duration == whole_note_duration / 2:
standard_notation = "1/2 Note"
elif note_duration == whole_note_duration / 4:
standard_notation = "1/4 Note"
elif note_duration == whole_note_duration / 8:
standard_notation = "1/8 Note"
elif note_duration == whole_note_duration / 16:
standard_notation = "1/16 Note"
elif note_duration == whole_note_duration / 32:
standard_notation = "1/32 Note"
elif note_duration == whole_note_duration / 64:
standard_notation = "1/64 Note"
elif note_duration == whole_note_duration / 128:
standard_notation = "1/128 Note"
return standard_notation
def read_midi_file(file_path):
mid = MidiFile(file_path)
global ppqn
ppqn = mid.ticks_per_beat
global whole_note_duration
whole_note_duration = ppqn * 4
global pattern_data_1
pos = 1
offset = 1
for i, track in enumerate(mid.tracks):
if track.name > '':
print(f"Track [{i:03}] Name: {track.name}")
for j, msg in enumerate(track):
if msg.type == "note_on":
note_on_time = msg.time
note_off_time = track[j + 1].time
note_duration = note_off_time - note_on_time
standard_notation = calculate_standard_notation(note_duration)
if standard_notation >"":
print(f'{pos:02X} {msg.note} {msg.velocity}')
pattern_data_1 += f'{pos:02X}{msg.note+offset:02X}{msg.velocity+offset:02X}{str(giggle)}'
else:
pos +=1
print(f'{pos:02X} {msg.note} {msg.velocity}')
pattern_data_1 += f'{pos:02X}{msg.note+offset:02X}{msg.velocity+offset:02X}{str(giggle)}'
elif msg.type == "note_off":
pos += 1
# for now just print the string to screen, check solution (update_xml.py) for method of finding and writing to a repatch file ;)
print(f'\n{pattern_data_1}')
if len(sys.argv) > 1:
# i'm still working on this, so there is defo bugs....
warningnotice()
# once i'm happy this will be removed
read_midi_file(sys.argv[1])
else:
print("Please provide a random rhythm midi filename.")
# ;) notice ='random rhythm file'
-
- RE Developer
- Posts: 1264
- Joined: 17 Jan 2015
- Location: Stockholm
I'm not sure if I understand what you mean by this?
The note start position is just the step number in hexadecimal, i e 01, 02, 03, 04, ..., 09, 0A, ..., 0F, 10, 11, 12, ..., 1F and so on, up to 80 (= 128 in decimal).
-
- RE Developer
- Posts: 1111
- Joined: 03 Jan 2019
Awesome!
If I get time on Saturday I’ll do a script for building PM patches in Python.
Cheers,
Brian
If I get time on Saturday I’ll do a script for building PM patches in Python.
Cheers,
Brian
-
- Information
-
Who is online
Users browsing this forum: No registered users and 0 guests