Hi!

Here is a script I've been working on, which converts Hydrogen drumkits to
BEAST drumkits. This is a nice way to improve drums in BEAST because

 - BEAST currently ships only one drumkit, Hydrogen has a few more
 - some Hydrogen drumkits sound really good (i.e. Hydrogen GMkit sounds
   better than our retrokit)
 - since debian (ubuntu) packages Hydrogen drumkits, there should be no license
   issues when importing/redistributing these
 - conversion needs not be done by the end user - we can simply ship
   the resulting .bsewave files

I've attached my script, usage information is at the start of the script.

I've attached a simple test drum loop, without the actual waves included, so
you can load GMkit into it and play with different kits. GMkit is a good start.

Limitations:

There is one thing that we need in BEAST before shipping imported kits: the
author, license, ... information is parsed by the script, but is not imported,
since bsewave xinfos for these do not exist. So I cannot import this
information, but we probably want to have it, so BEAST should have xinfos and
read xinfos and display it somewhere in the UI.

Since the Hydrogen kits are multilayered (different gain implies different wave
files), and since BEAST afaik doesn't support this, I use only the settings for
the loudest layer - but that should be OK. Also there are a few settings I
ignore during import, but as long as the resulting kits sound good, that should
be OK too.

   Cu... Stefan
-- 
Stefan Westerfeld, http://space.twc.de/~stefan
#!/usr/bin/env python

# Hydrogen to BEAST drumkit converter
#
# Usage:
#
#   apt install hydrogen hydrogen-drumkits
#
#   himport.py             # list all available hydrogen drumkits
#   himport.py GMkit       # import one drumkit (from list)
#   himport.py all         # import all drumkits
#
# Author: Stefan Westerfeld <[email protected]>
#
# CC0 Public Domain: http://creativecommons.org/publicdomain/zero/1.0

import xml.etree.ElementTree as ET
import os
import sys
import subprocess

def die (message):
  print >> sys.stderr, "himport.py: " + message
  exit (1)

def system_or_die (command):
  print "+++ %s" % command
  return_code = subprocess.call (command, shell=True)
  if return_code != 0:
    die ("executing command '%s' failed, return_code=%d" % (command, return_code))

def get_channels (filename):
  return int (subprocess.check_output (["soxi", "-c", filename]))

def get_format (filename):
  return subprocess.check_output (["soxi", "-t", filename]).strip()

def list_kits():
  all_kits = []
  for kit in os.listdir ("/usr/share/hydrogen/data/drumkits"):
    try:
      os.stat ("/usr/share/hydrogen/data/drumkits/%s/drumkit.xml" % kit)
      all_kits.append (kit)
    except:
      pass
  return all_kits

class HydrogenKit:
  pass

class HydrogenNote:
  pass

def normalize_tag (child):
  name = child.tag
  # some (newer) drumkit tags use namespace: http://www.hydrogen-music.org/drumkit
  if name[0] == "{":
    uri, tag = name[1:].split("}")
    if uri == "http://www.hydrogen-music.org/drumkit":
      return tag
    else:
      die ("normalize_tag: found unsupported namespace %s" % uri)
  else:
    # no namespace means we're reading an old file without namespaces
    return name

def parse_kit (dir_name):
  kit_name = dir_name
  kit_dir = "/usr/share/hydrogen/data/drumkits/" + kit_name
  kit = kit_dir + "/drumkit.xml"

  tree = ET.parse (kit)
  root = tree.getroot()

  instrument_index = 0

  hydrogen_kit = HydrogenKit()
  hydrogen_kit.notes = []
  for child in root:
    if normalize_tag (child) == "name":
      hydrogen_kit.name = child.text
    elif normalize_tag (child) == "author":
      hydrogen_kit.author = child.text
    elif normalize_tag (child) == "info":
      hydrogen_kit.info = child.text
    elif normalize_tag (child) == "license":
      hydrogen_kit.license = child.text
    elif normalize_tag (child) == "instrumentList":
      for il_child in child:
        # import each instrument
        if normalize_tag (il_child) == "instrument":
          note_filename = None
          note_volume = 1
          note_gain = 1

          for ichild in il_child:
            if normalize_tag (ichild) == "volume":
              note_volume = float (ichild.text)
            if normalize_tag (ichild) == "layer":
              for lchild in ichild:
                if normalize_tag (lchild) == "min":
                  layer_min = float (lchild.text)
                if normalize_tag (lchild) == "max":
                  layer_max = float (lchild.text)
                if normalize_tag (lchild) == "filename":
                  layer_filename = lchild.text
                if normalize_tag (lchild) == "gain":
                  layer_gain = float (lchild.text)
              if layer_min <= 0.9999 and layer_max >= 0.9999:
                # only import layer with the loudest sample
                note_filename = layer_filename
                note_gain = layer_gain
            if normalize_tag (ichild) == "filename":
              # legacy: old hydrogen drumkits have no layers, only one global filename
              note_filename = ichild.text

          if note_filename:
            hydrogen_note = HydrogenNote()
            hydrogen_note.index = instrument_index
            hydrogen_note.filename = note_filename
            hydrogen_note.volume = note_volume
            hydrogen_note.gain = note_gain

            try:
              os.stat (kit_dir + "/" + note_filename)

              hydrogen_kit.notes.append (hydrogen_note)
            except:
              # we don't die here because there are broken drumkits that reference non-existent files
              print "WARNING: soundfile %s missing, not importing that instrument" % (kit_dir + "/" + note_filename)
          else:
            print "note_filename", note_filename
            print "note_volume", note_volume
            print "note_gain", note_gain
            print "WARNING: instrument from kit %s could not be imported, missing required fields" % kit_name

          instrument_index += 1
        else:
          print "UNPARSED INSTRUMENT LIST CHILD:", il_child.tag
    else:
      print "UNPARSED CHILD:", child.tag
  return hydrogen_kit

def do_import (dir_name):
  kit_name = dir_name
  kit_dir = "/usr/share/hydrogen/data/drumkits/" + kit_name
  kit = kit_dir + "/drumkit.xml"
  bsewave = kit_name + ".bsewave"

  hydrogen_kit = parse_kit (dir_name)

  # figure out number of channels for this drumkit
  channels = 0
  for note in hydrogen_kit.notes:
    note.channels = get_channels (kit_dir + "/" + note.filename)

    channels = max (channels, note.channels)

  if channels != 1 and channels != 2:
    die ("unsupported channels: %d" % channels)

  system_or_die ("rm -f '%s'" % bsewave)
  system_or_die ("bsewavetool create '%s' %d" % (bsewave, channels))
  system_or_die ("bsewavetool xinfo '%s' --wave play-type=plain-wave-%d label='%s'" % (bsewave, channels, kit_name))

  for note in hydrogen_kit.notes:
    full_filename = kit_dir + "/" + note.filename

    print "importing note %d, filename %s, channels %d" % (note.index, note.filename, note.channels)

    # mono to stereo conversion is necessary if some notes are mono and others
    # are stereo in order to get a working bsewave in this case, we convert all
    # mono files to stereo automatically
    convert_to_stereo = (note.channels == 1 and channels == 2)
    if convert_to_stereo:
      tmp_filename = "himport.tmp%d.wav" % os.getpid()
      system_or_die ("sox '%s' -c 2 '%s'" % (full_filename, tmp_filename))
      full_filename = tmp_filename

    # convert every file that is not flac to flac
    convert_to_flac = get_format (full_filename) != "flac"
    if (convert_to_flac):
      tmp_flac_filename = "himport.tmp%d.flac" % os.getpid()
      system_or_die ("sox '%s' '%s'" % (full_filename, tmp_flac_filename))
      full_filename = tmp_flac_filename

    system_or_die ("bsewavetool add-chunk '%s' -m=%d '%s'" % (bsewave, note.index + 36, full_filename))
    system_or_die ("bsewavetool xinfo '%s' -m=%d volume=%f" % (bsewave, note.index + 36, note.volume * note.gain))

    if convert_to_stereo:
      os.unlink (tmp_filename)
    if convert_to_flac:
      os.unlink (tmp_flac_filename)

if len (sys.argv) == 2:
  if sys.argv[1] == "all":
    for kit in list_kits():
      do_import (kit)
  else:
    do_import (sys.argv[1])
else:
  print "usage: himport.py <kit_name>"
  print
  print "where kit_name is one of the drumkits in /usr/share/hydrogen/data/drumkits:"
  for kit in list_kits():
    print "  " + kit
; BseProject

(bse-version "0.10.1")

(container-child "BseWaveRepo::Wave-Repository"
  (modification-time "2017-05-12 10:35:40")
  (creation-time "2017-05-12 10:35:40")
  (license "Creative Commons Attribution 2.5 
(http://creativecommons.org/licenses/by/2.5/)")
  (author "Stefan Westerfeld")
  (container-child "BseWave::GMkit"
    (load-wave "/home/stefan/src/sandbox/himport/GMkit.bsewave" "GMkit"))
  (container-child "BseWave::retrokit"
    (load-wave "/usr/local/beast/share/beast/samples/retrokit.bsewave" 
"retrokit")))
(container-child "BseSoundFontRepo::Sound-Font-Repository"
  (modification-time "2017-05-12 10:35:40")
  (creation-time "2017-05-12 10:35:40")
  (license "Creative Commons Attribution 2.5 
(http://creativecommons.org/licenses/by/2.5/)")
  (author "Stefan Westerfeld"))
(container-child "BseSong::Titel"
  (bpm 120)
  (musical_tuning OD_12_TET)
  (auto_activate 1)
  (loop-right 3072)
  (loop-left 0)
  (loop-enabled #t)
  (denominator 4)
  (numerator 4)
  (tpqn 384)
  (modification-time "2017-05-12 10:35:56")
  (creation-time "2017-05-12 10:35:56")
  (license "Creative Commons Attribution 2.5 
(http://creativecommons.org/licenses/by/2.5/)")
  (author "Stefan Westerfeld")
  (container-child "BsePart::Part-1"
    (n-channels 2)
    (insert-notes 0
      (0x00000 0x180 36)
      (0x000c0 0x0c0 42)
      (0x00180 0x180 38)
      (0x00240 0x0c0 42)
      (0x00300 0x0bf 36)
      (0x003c0 0x0bf 36)
      (0x00480 0x180 38)
      (0x00540 0x0c0 42))
    (insert-notes 1
      (0x00000 0x0bf 42)
      (0x00180 0x0c0 42)
      (0x00300 0x0c0 42)
      (0x003c0 0x0c0 42)
      (0x00480 0x0c0 42)))
  (container-child "BseBus::Master-1"
    (master-output #t)
    (right-volume 1)
    (left-volume 1)
    (bus-input (link 1 "Track-01")))
  (container-child "BseTrack::Track-01"
    (n-voices 16)
    (wave (link 2 "Wave-Repository:GMkit"))
    (insert-part 0 (link 1 "Part-1"))
    (insert-part 1536 (link 1 "Part-1"))))
(container-child "BseCSynth::BQS Reverb"
  (auto_activate 0)
  (modification-time "2017-05-12 10:38:53")
  (creation-time "2005-08-07 14:23:05")
  (license "Provided \\\"as is\\\", WITHOUT ANY WARRANTY 
(http://beast.testbit.eu/LICENSE-AS-IS)")
  (author "Stefan Westerfeld")
  (container-child "BseSubIPort::SubIPort-1"
    (pos-y 1.04)
    (pos-x -4.21))
  (container-child "BseSubOPort::SubOPort-1"
    (pos-y 1.07)
    (pos-x 2.13)
    (source-input "input-1" (link 1 "FreeVerb-1") "left-audio-out")
    (source-input "input-2" (link 1 "FreeVerb-1") "right-audio-out"))
  (container-child "BseFreeVerb::FreeVerb-1"
    (width 100)
    (dry-level 1)
    (wet-level 0.34796237945556641)
    (damping 40)
    (room-size 0.8399999737739563)
    (pos-y 1.07)
    (pos-x -1.09)
    (source-input "left-audio-in" (link 1 "SubIPort-1") "output-1")
    (source-input "right-audio-in" (link 1 "SubIPort-1") "output-2")))
_______________________________________________
beast mailing list
[email protected]
https://mail.gnome.org/mailman/listinfo/beast

Reply via email to