Sysex dump sample (a.k. Kick oneshot file) to MD-SPS1, Machinedrum

The Harlekin's icon

!!! It looks more complicated then it probably is !!! PLEASE, give me a. lift !!! Hi,

i've been looking up this for quite some time now but it seems to me that after all these years no one has come up with a solution on how to transfare a file (a.k. kick drum sample) via max/maxforlive to the elektron machinedrum sps-1. I collected quite some stuff from the forum here but can't get my head around how to put it together. The most difficult part it seems is the 8to7 bit conversion. It would be outragous if any of you who sees the forrest and not just the trees as I do and gets this solved. Of course there is a solution but as neither max is nor am I c++, I can't translate this to its javascript equival, which would be one solutuion taken from the uwedit / MidiSDS.cpp / SaveMidiSDS() on github:

const int SAMPLES_PER_PACK = 40; // only 40 16bit samples can fit in the packed 120 7bit bytes
    size_t datapos = 0;
    for (int packetSeq = 0; datapos < data.size(); packetSeq++)
    {
        buffer[0] = 0xf0;
        buffer[1] = 0x7e;
        buffer[2] = sysExChannel;
        buffer[3] = 0x02;
        buffer[4] = packetSeq % 128;

        unsigned char *b = &buffer[5];
        for (int t = 0; t < SAMPLES_PER_PACK; t++)
        {
            int s = 0;

            if (datapos < data.size())
                s = data[datapos++] + 0x8000;
            else
                s = 0;

            (*(b++)) = (s >> 9) & 0x7f;
            (*(b++)) = (s >> 2) & 0x7f;
            (*(b++)) = s & 0x3;   
        }

        // calc checksum
        unsigned char ll = 0;
        for (int t = 1; t < 125; t++)
            ll ^= buffer[t];

        ll &= 0x7f;

        buffer[125] = ll;
        buffer[126] = 0xf7;

        l = write(buffer, 127);
        if (l != 127)
        {
            return false;
        }

        if (progress)
        {
                progress(((float)datapos) / data.size());
        }
    }

I also found this script, which from my understanding will/must covers a core function in this task if there is conversion involved ( credits for this go to who ever took on this challenge already ):

autowatch = 1;

////////////////////////////////////////////////////////////////////////////
// Converts 8 bytes of "packed MS bit" sysex into the original 7 bytes
//
// Input (8 bytes):
//  7  6  5  4  3  2  1  0 bits
// 00 G7 F7 E7 D7 C7 B7 A7
// 00 A6 A5 A4 A3 A2 A1 A0
// 00 B6 B5 B4 B3 B2 B1 B0
// 00 C6 C5 C4 C3 C2 C1 C0
// 00 D6 D5 D4 D3 D2 D1 D0
// 00 E6 E5 E4 E3 E2 E1 E0
// 00 F6 F5 F4 F3 F2 F1 F0
// 00 G6 G5 G4 G3 G2 G1 G0
//
// Output (7 bytes):
//  7  6  5  4  3  2  1  0
// A7 A6 A5 A4 A3 A2 A1 A0
// B7 B6 B5 B4 B3 B2 B1 B0
// C7 C6 C5 C4 C3 C2 C1 C0
// D7 D6 D5 D4 D3 D2 D1 D0
// E7 E6 E5 E4 E3 E2 E1 E0
// F7 F6 F5 F4 F3 F2 F1 F0
// G7 G6 G5 G4 G3 G2 G1 G0
////////////////////////////////////////////////////////////////////////////

function printHexArray(arr, linelen) {
  var str = "";
  if (!linelen) linelen = 7;
  for (var i = 0; i < arr.length; i++) {
    if (i % linelen == 0) str += "\n"
    str += "0x" + ("0" + arr[i].toString(16)).slice(-2) + " ";
  }
  return str;
}

// assumed that you've already skipped over any header bytes, removed any
// footer bytes, etc. This is just processing the raw data.

// this is MIDI 7-bit data
var dataBuffer = [ 0x7F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
                   0x55, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
                   0x2A, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40 ];

// this is the expected output of the algorithm applied to the above data
var expectedOutput = [ 0x81, 0x82, 0x84, 0x88, 0x90, 0xA0, 0xC0,
                       0x81, 0x02, 0x84, 0x08, 0x90, 0x20, 0xC0,
                       0x01, 0x82, 0x04, 0x88, 0x10, 0xA0, 0x40 ];

post("original:" + printHexArray(dataBuffer, 8) + "\n\n");

var outputBuffer = [];
var count = 0;
var highBits = 0;

// this is the conversion algorithm
for (var i = 0; i < dataBuffer.length; i++) {
  var row = i % 8; // relative row in this group of 8 bytes
  if (!row) { // first row
    highBits = dataBuffer[i];
  }
  else {
    var highBit = highBits & (1 << (row - 1));
    highBit <<= (8 - row); // shift it to the 7th bit
    outputBuffer[count++] = dataBuffer[i] | highBit;
  }
}

post("unpacked:" + printHexArray(outputBuffer) + "\n\n");
post("expected:" + printHexArray(expectedOutput) + "\n\n");

// just for fun, let's pack it back up
var packedBuffer = [];
var MSidx = 0;
count = 0;

for (var i = 0; i < outputBuffer.length; i++) {
  var row = i % 7; // relative row in this group of 7 bytes
  if (!row) {
    MSidx = count; // cache the position of the MSB byte
    packedBuffer[count++] = 0;
  }
  var highBit = outputBuffer[i] & 0x80;
  highBit >>= 7 - row; // position this bit in the MSB byte
  packedBuffer[MSidx] |= highBit;
  packedBuffer[count++] = outputBuffer[i] & 0x7F;
}

post("repacked:" + printHexArray(packedBuffer, 8) + "\n");
MD-SPS1_SysEx_v0.9.pdf
pdf

Here you'll see right at the first page at the bottom why I think that the above conversion somehow is needed.Here is as far as I got and If anyone out there could give me the missing link it would be one big of a favour I can not even describe:

// inlets and outlets
inlets = 1;
outlets = 2;

autowatch = 1;
 
var fileQueue = [];
var buffer = new Buffer("sampleBuffer")
var midiPort = 1; // Adjust to your MIDI output port for Machinedrum
var bufferSize = 120; // MIDI SDS chunk size
var scaleFactor = 2047; // 12-bit conversion factor
var currentFile = "";
var totalFrames = 0;
var processedFrames = 0;
var bfSR = [];

function loadBatch() {
	post("jsarguments.length: " +jsarguments.length + "\n", "argument 0: " + jsarguments[0] + "\n" );
	outlet(1, "jsarguments: " + jsarguments);
    if (jsarguments.length < 1) {
        post("No files provided.\n");
        return;
    }

    fileQueue = [];
    for (var i = 0; i < jsarguments.length; i++) {
        fileQueue.push(jsarguments[i]);
    }

    post("Loaded batch: ", fileQueue.length, " files.\n");
    processNextFile();
}

function processNextFile() {
    if (fileQueue.length === 0) {
        post("Batch processing complete.\n");
        outlet(1, "progress", "done");
        return;
    }

    currentFile = fileQueue.shift();
    post("Processing file: ", currentFile, "\n");
    outlet(1, "progress", "loading", currentFile);
	buffer.send("replace",currentFile);
    
	if (buffer.framecount() > 0) {
        totalFrames = buffer.framecount();
        processedFrames = 0;
        resampleTo44100Mono();
    } else {
        post("Error loading file: ", currentFile, "\n");
        processNextFile(); // Move to next file even if failed
    }
}

function resampleTo44100Mono() {
    var sr = buffer.samplerate();
    var channels = buffer.channelcount();

    if (sr !== 44100 || channels > 1) {
        post("Resampling to 44.1kHz and converting to mono...\n");
        outlet(1, "progress", "resampling");

        var tempBuffer = new Buffer("convertedBuffer");
		tempBuffer.send("sizeinsamps",totalFrames,1);

        for (var i = 0; i < totalFrames; i++) {
            var left = buffer.peek(1, i);
            var right = channels > 1 ? buffer.peek(2, i) : left;
            var monoSample = (left + right) / 2;
            tempBuffer.poke(1, i, monoSample);
        }

        tempBuffer.send('sr',44100);
		tempBuffer.send("bang");
        convertTo12bit(tempBuffer);
    } else {
        convertTo12bit(buffer);
    }
}

function convertTo12bit(buffer) {
	var frameCount = buffer.framecount();
    var convertedBuffer = new Buffer("converted12bit");
    convertedBuffer.send("sizeinsamps",frameCount, 1);

    for (var i = 0; i < frameCount; i++) {
        var sample = buffer.peek(1, i);
        var convertedSample = Math.round(sample * scaleFactor) / scaleFactor;
        convertedBuffer.poke(1, i, convertedSample);

        // Update progress every 1000 samples
        if (i % 1000 === 0) {
            processedFrames = i;
            updateProgress();
        }
    }
	convertedBuffer.send("bang");
    post("Conversion to 12-bit done.\n");
    outlet(1, "progress", "converting", "done");
    sendSample(convertedBuffer, frameCount);
}

function sendSample(buffer, frameCount) {
    var header = [0xF0, 0x00, 0x3C, 0x02, 0x00]; // Elektron SDS Header
	var kit = [0x52, 0x04, 0x01];
	var smplTransHead = [0xF0,0x7E,0x00,0x01];
	var smplTransInit = [0x00,0x00,0x10,0x14,0x31,0x01,0x16,0x4A,0x00,0x00,0x00,0x00,0x15,0x4A,0x00,0x7F,0xF7]
    var footer = [0xF7]; // End of SysEx

    processedFrames = 0;
    totalFrames = frameCount;
	post("Conversion to totalFrames: " + totalFrames + "\n");
    for (var i = 0; i < frameCount; i += bufferSize) {
        var chunk = [];

        for (var j = 0; j < bufferSize && (i + j) < frameCount; j++) {
            chunk.push(Math.floor(buffer.peek(1, i + j) * 127)); // MIDI 7-bit conversion
			post(chunk);
        }
		
        var sysExData = header.concat(chunk, footer);
        
		outlet(0, sysExData);

        // Update progress every chunk
        processedFrames = i;
        updateProgress();
    }

    post("Sample sent to Machinedrum.\n");
    outlet(1, "progress", "sending", "done");
    processNextFile(); // Move to next file after sending
}

function updateProgress() {
    var percentage = Math.floor((processedFrames / totalFrames) * 100);
    outlet(1, "progress", "processing", percentage);
}

// For debugging: print any received integers to the post window
function msg_float(v) {
    post("Received float: " + v + "\n");
}

// Max for Live Inputs
/*
function anything() {
	post("jsarguments: " + jsarguments + "\n");
    var args = arrayfromargs(messagename, arguments);
	post("messagename: " + messagename + "\n", "arguments: " + args + "\n");
    if (args[0] === "batch") {
		post("Found batch\n");
        jsarguments = args;
		post("!! jsarguments: " + jsarguments + "\n", "!! jsarguments 0: " + jsarguments[0] + "\n", "!! jsarguments 1: " + jsarguments[1] + "\n", "!! jsarguments 2: " + jsarguments[2] + "\n");
        loadBatch();
    }
}
*/

function list(){
	var a = arrayfromargs(messagename, arguments);
	post('args',a);
	post("Buffer: ", bfn, "| Value: ", bfv,"\n");
	bfSR[bfn] = bfv;
}

function anything() {

	var a = arrayfromargs(messagename,arguments);
	if (a[0] === "dropFile") {
		outlet(1,"Preparing file: ",a[1]);
		fileQueue.push(a[1]);
		post("Loaded batch: ",a[1], fileQueue.length, " files.\n");
    	processNextFile();
	} else if(a[0] === "smplBf"){
		bfSR[a[1]] = a[2];
		post("Recived SR:",a[1],"|", a[2], ".\n");
		
	};
		

}

Buffer.prototype.samplerate = function(){
    return 1000*this.framecount()/this.length()
}

Peace & only the best for all of u !!!
Josh