Python Script:sliced beats .xrni to .mid

I’ll admit this is a very quick and rough hack written for my own use, but I thought I’d share it as a starting point for others to work from. It seems to work with Renoise 3.3.2 – as it is, it works for me, but isn’t particularly robust – in my renoise by default the first slice is midi note 37, so it assumes that the slices are mapped one per semitone starting from 37. It assumes things are laid out in the Instrument.xml as they are with the current versions of renoise (3.3.2) and redux (1.3.2).

It would be nice to be able to drag and drop midi for a sliced break into the DAW. This is a poor-man’s hack of a workaround: a simple Python script, that currently calls csvmidi to convert to midi It opens the .xrni, extracts the length of the clip in samples, and the sample position of each slice (assuming that each slice ends where the next begins), and produces a .csv file to be input into csvmidi (midicsv and csvmidi convert between .mid files and a textual CSV representation).

e.g. If I load "AUSP - Loops - 85BPM - London 2044 1 .wav’ (from Airwave’s Ultimate Pack), and slice in Renoise to produce AUSP_London2044_001.xrni, then (noting by ear that this is 16 beats at 85bpm – I’m not going to try to write code that guesses, as a file of this length could easily be 32 beats at 170bpm)

python3 slice_xrni_to_mid.py 16 AUSP_London2044_001.xrni

will produce

AUSP_London2044_001.mid

that you can drag into your DAW.

As for python, any recent version of python3 will do – on Windows, this means either the official python distribution (provided it’s in your PATH), or cygwin, or anaconda, or WSL2, and on macos I think a new enough python is bundled. Then you need csvmidi from MIDICSV: Convert MIDI File to and from CSV

(It should be easy enough to modify it to use a python standard midi file module instead of midicsv)

==== CODE for slice_xrni_to_mid.py

#!/usr/bin/env python3
import re
import sys
from zipfile import ZipFile
from subprocess import run

def print_usage():
  print(f"{sys.argv[0]} <num beats> [<instr1.xrni> <instr2.xrni> ...]")

def main():
  try:
    args = sys.argv[2:]
    nbeats = int(sys.argv[1])
  except IndexError:
    print_usage()
    exit(1)
  for arg in args:
    if not arg.endswith(".xrni"):
      print(f"{arg} is not a .xrni file")
    else:
      ifn = arg
      csv = re.sub(r"\.xrni$",".csv",ifn)
      ofn = re.sub(r"\.xrni$",".mid",ifn)
      procfile(ifn,csv,ofn,nbeats)

def get_num_from(x):
  m = re.search(r"\d+",x)
  if m:
    return int(m.group(0))
  else:
    return None

def procfile(ifn,csv,ofn,num_beats,ppqn=960):
  try:
    zf = ZipFile(ifn)
    with zf.open("Instrument.xml") as f:
      xml = f.read().decode()
      lines = xml.splitlines()
      st = None
      en = None
      spos = []
      for line in lines:
        if "LoopStart" in line and st is None:
          st = get_num_from(line)
        if "LoopEnd" in line and en is None:
          en = get_num_from(line)
        if "SamplePosition" in line:
          spos.append(get_num_from(line))
    spos_float = [ num_beats * x / (en - st) for x in spos ] # compute start position as float fractions of a beat
    spos_start_tick = [ int(ppqn*x) for x in spos_float ]
    spos_end_tick = [ x - 1 for x in (spos_start_tick+[ppqn*num_beats])[1:] ]
    header = [0, 0, "Header", 0, 1, ppqn]
    track = [[1, 0, "Start_Track"]]
    first_note = 37
    for i,xs in enumerate(zip(spos_start_tick,spos_end_tick)):
      s,e = xs
      track.append([1,s,"Note_on_c",0,first_note+i,100])
      track.append([1,e,"Note_off_c",0,first_note+i,0])
    track.append([1,num_beats*ppqn, "End_track"])
    footer = [0,0,"End_of_file"]
    with open(csv,"wt") as f:
      pl(header,f)
      for line in track:
        pl(line,f)
      pl(footer,f)
    run(["csvmidi",csv,ofn])
    print(f"Done {ifn} => {ofn}")
  except Exception as e:
    print(f"Processing {ifn} failed with exception {e}")

def pl(line,f):
  print(", ".join(map(str,line)),file=f)

if __name__ == "__main__":
  main()
2 Likes

Hello,
Thanks for this
but you must this code set into any tags with formating.
Because python is strictly formated, with this form is script unusable.
Or better upload source file.
thanks.

Sorry about that. Fixed.

1 Like