Last active
April 18, 2021 22:14
-
-
Save grinsted/3fb39aa61accbe8ef2c1aa678c074153 to your computer and use it in GitHub Desktop.
Tool for quickly making drumkits for the novation circuit
This file contains 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
# This file contains code to save 64 samples as a syx file for Novation Circuit | |
# | |
# * It reads a single long wave file. | |
# * splits it into 64 equal chunks | |
# * crops the sound | |
# * adds a super fast fadein and "an appropriate fadeout". To remove clicks | |
# * normalizes every sample | |
# * Saves the whole thing as a syx file. | |
# | |
# Aslak Grinsted 2020 | |
finput = 'fast_circuit_drumkit2.wav' # this has to be 48khz! There is no resampling. | |
foutput = finput.replace('.wav','.syx') | |
from scipy.io import wavfile | |
samplerate, data = wavfile.read(finput) | |
import matplotlib.pyplot as plt | |
import numpy as np | |
import binascii | |
import math | |
import struct | |
#Utility functions to write the NOVATION CIRCUIT sysex file | |
def crcToNybbles(crc): | |
n = [0, 0, 0, 0, 0, 0, 0, 0] | |
for t in range(8): | |
n[t] = 15 & crc >> 4 * (7 - t) | |
return bytearray(n) | |
# def sevenToEight(packet): | |
# packet = [b for b in packet] | |
# data = bytearray(b"") | |
# n = len(packet) | |
# r = 0 | |
# while r < n: | |
# i = packet[r : r + 8] | |
# c = i[1:] | |
# for a in range(len(c)): | |
# hi = (i[0] & (1 << a)) >> a | |
# c[a] += hi << 7 | |
# r += 8 | |
# data.extend(c) | |
# return data | |
def eightToSeven(e): | |
n = len(e) | |
r = n + math.ceil(n / 7) | |
a = [0] * r | |
o = 0 | |
i = 0 | |
while o < n: | |
for t in range(7): | |
c = 0 | |
if o + t < n: | |
a[i + t + 1] = 127 & e[o + t] | |
c = (128 & e[o + t]) >> 7 - t | |
a[i] |= c | |
o += 7 | |
i += 8 | |
return bytearray(a) | |
#THIS IS A SAMPLE IN THE CIRCUIT SAMPLE BANK | |
class Sample: | |
def __init__(self): | |
self.sampledata = [] # has to be 48khz | |
def set_sampledata(self, data): | |
if len(data.shape)>1: | |
data = np.mean(data, axis=1) #make mono | |
data = data / np.max(np.abs(data)) | |
self.sampledata = (data * 32767).astype(np.int16) | |
def blob(self): | |
n = len(self.sampledata) * 2 | |
sampleblob = [1, 16, 128, 187, 0, 0] | |
sampleblob.extend([n & 0xFF, (n >> 8) & 0xFF, (n >> 16) & 0xFF, n >> 24]) | |
if n > 0: | |
raw = struct.pack(">{}h".format(len(self.sampledata)), *self.sampledata) | |
sampleblob.extend(raw) | |
return sampleblob | |
#CLASS HOLDING 64 SAMPLES | |
class SampleBank: | |
samples = [] | |
def __init__(self): | |
self.samples = [Sample() for count in range(64)] | |
def blob(self): | |
blob = [64] | |
for sample in self.samples: | |
blob.extend(sample.blob()) | |
# length must be 5763072 | |
if len(blob) > 5763072: | |
raise Exception("insufficient space!") | |
blob.extend([0] * (5763072 - len(blob))) | |
return blob | |
def sysex(self): | |
blob = self.blob() | |
crc = binascii.crc32(bytearray(blob)) | |
sysex = list(b'\xF0\x00\x20\x29\x00\x77\x00\x00\x02\x03\x0B\x00\x00\x00\x00\x00\x05\x07\x0F\x00\x00\x00\xF7') | |
for offset in range(0,5763072,256): | |
row = blob[offset:offset+256] | |
row = eightToSeven(row) | |
sysex.extend(list(b'\xF0\x00\x20\x29\x00\x79')) | |
sysex.extend(row) | |
sysex.extend(list(b'\xF7')) | |
sysex.extend(list(b'\xF0\x00\x20\x29\x00\x7A')) | |
sysex.extend(crcToNybbles(crc)) | |
sysex.extend(list(b'\xF7')) | |
return bytearray(sysex) | |
Bank = SampleBank() | |
#THIS CODE reads a single long wave file and splits it into 64 individual samples | |
N=64 # | |
Ns = int(data.shape[0]/N) | |
fadein =8 | |
for ix in range(N): | |
s = data[Ns*ix:Ns*(ix+1)-20,:] | |
s = s/np.max(s) | |
#s = s.mean(axis=1) | |
lastix = np.where(np.abs(s)>0.02)[0][-1] | |
fadeout=int(lastix/10) | |
for c in range(s.shape[1]): | |
s[0:fadein,c] = s[0:fadein,c] * np.linspace(0,1,fadein) | |
s[lastix-fadeout:lastix,c] = s[lastix-fadeout:lastix,c] * np.linspace(1,0,fadeout) | |
s = s[0:lastix,:] | |
Bank.samples[ix].set_sampledata(s) | |
#THESE lines saves individual samples. | |
# kit = int(ix/8) + 1 | |
# sample = (ix) % 8 +1 | |
# fname = 'drum{}_kit{}.wav'.format(sample,kit) | |
# s = (s*32767).astype(data.dtype) | |
# wavfile.write(fname,samplerate,s) | |
syxfile = open(foutput, "wb") | |
syxfile.write(Bank.sysex()) | |
syxfile.close() |
Nice .. I can see you have also cleaned up the code alot.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
You inspired me, I create a Python module/app to pack/unpack samples and to experiment what the circuit is actually capable of.
https://github.com/mungewell/circuit_samples
Example use would be