UPDATE: Perl script to convert sound files into OP-1 / OP-Z SYNTH or DRUM samples

I wrote a perl script that adds a header to aif files such that they can be loaded into OP-Z on tracks 5 - 8. Works for me under Windows and Linux, haven’t tried it under IOS or OSX. Save the program text below (without the BEGIN and END lines) to a file, e.g. TE_patch.pl and call as:

perl TE_patch.pl {input: name of original aif file} {output: name of patched file}

The input file should not be longer than 6 seconds, the output file gets overwritten if it exists already. You can also use the script in a batch file to convert multiple files in one go, e.g. “for %f in (my*.aif) do perl TE_patch.pl %f %~nf_TE.aif” or similar on Windows. (Of course, you must have a Perl interpreter installed.)

--------------------BEGIN PROGRAM TEXT------------------------------------
my @TE_patch = (65,80,80,76,0,0,1,106,111,112,45,49,123,34,97,100,115,
114,34,58,91,50,54,50,52,44,50,50,53,55,54,44,50,54,54,50,51,44,
54,55,50,48,44,52,48,48,48,44,54,52,48,48,44,52,48,48,48,44,52,
48,48,48,93,44,34,98,97,115,101,95,102,114,101,113,34,58,52,52,48,
46,48,44,34,102,120,95,97,99,116,105,118,101,34,58,102,97,108,115,101,
 44,34,102,120,95,112,97,114,97,109,115,34,58,91,56,56,57,54,44,49,
52,56,49,54,44,49,53,51,54,44,54,54,56,56,44,56,48,48,48,44,56,
48,48,48,44,56,48,48,48,44,56,48,48,48,93,44,34,102,120,95,116,121,
112,101,34,58,34,115,112,114,105,110,103,34,44,34,107,110,111,98,115,34,
58,91,48,44,50,56,52,52,56,44,51,50,53,48,51,44,51,50,53,48,51,44,
49,50,48,48,48,44,48,44,48,44,57,56,51,50,93,44,34,108,102,111,95,
97,99,116,105,118,101,34,58,102,97,108,115,101,44,34,108,102,111,95,112,
97,114,97,109,115,34,58,91,50,48,48,48,44,50,54,51,48,52,44,53,55,
50,56,44,49,52,52,54,52,44,48,44,48,44,48,44,48,93,44,34,108,102,
111,95,116,121,112,101,34,58,34,101,108,101,109,101,110,116,34,44,34,110,
97,109,101,34,58,34,50,48,49,52,48,52,48,55,34,44,34,111,99,116,97,
118,101,34,58,49,44,34,115,121,110,116,104,95,118,101,114,115,105,111,110,
34,58,49,44,34,116,121,112,101,34,58,34,115,97,109,112,108,101,114,34,
125,10,32,83,83,78,68);

my $insert = "";
while (@TE_patch) { $insert = $insert . chr ( shift @TE_patch ) };

open H, $ARGV[0];
binmode H;
my $new = do { local $/;  };
close H;

my $p = -1;
for (my $l = 0; $l < length ($new); $l++) {
	my $d = substr $new, $l, 4;
	if ($d eq "SSND") {
		$p = $l;
	}
} 

if ($p == -1) { print "No sound marker SSND found in $ARGV[0]
"; exit; }

open K, ">", $ARGV[1];
binmode K;
print K (substr $new, 0, $p), $insert, (substr $new, ($p + 4));
close K;
--------------------END PROGRAM TEXT------------------------------------

Hi,
interesting. I did basically the same thing in JS for the browser, find the thread here:
https://operator-1.com/index.php?p=/discussion/4726/op-z-custom-chromatic-samples-without-op-1-hacky-solution

Do you have an OP-1 by any chance? I found out today that the knobs parameter in the marker json handles sample looping but I’m having trouble deciphering the actual values. Would like to have some material from an OP-1 to check the values when only loop points change etc.

Cheers!

interesting. I did basically the same thing in JS for the browser, find the thread here:
https://operator-1.com/index.php?p=/discussion/4726/op-z-custom-chromatic-samples-without-op-1-hacky-solution

yes, I saw that; it worked for me once and never again, don’t know why. Maybe a JS permissions conflict… ?

Do you have an OP-1 by any chance? I found out today that the knobs parameter in the marker json handles sample looping but I’m having trouble deciphering the actual values. Would like to have some material from an OP-1 to check the values when only loop points change etc.

As far as I know, changing loop points and looping behaviour on the OP-1 does only work for 12sec drum kits, doesn’t it? My script though works only on 6 sec synth samples. In principle, the method of our scripts should be also applicable to drum sample kits. However, drum kits have different and more parameters.

Also, I’m using just one json template for all my synth samples and therefore have to adjust adsr etc manually on the OP-Z (except base_freq and octave, those parameters have to be set properly in the script for each synth sample or it will sound out of tune or in the wrong octave).

This is the ascii part of an APPL patch that could be used for synth samples (example). Not sure if there is any information about loop points in there:

{
“adsr”:[512,10746,32767,10000,4000,64,4000,4000],
“base_freq”:391.99511718750,
“fx_active”:false,
“fx_params”:[1344,11008,16384,11263,8000,8000,8000,8000],
“fx_type”:“spring”,
“knobs”:[0,0,32767,32767,12000,0,0,11583],
“lfo_active”:true,
“lfo_params”:[2000,32767,2000,2000,0,0,0,0],
“lfo_type”:“element”,
“name”:“2019_0727”,
“octave”:0,
“synth_version”:2,
“type”:“sampler”
}

“knobs” is where the loop point information is stored. I’m not sure what the numbers refer to, as I don’t have an OP-1, but there doesn’t seem to be a parameter for activating the loop…?

I’m hard at work on this right now. The second parameter for knobs is the loop start point, the third is the end point. Some of the others probably triggers if there is a loop at all. They’re 15 bit and for some reason, 32767 as end point doesn’t work, so 32766 is the max loop Iength I can get. I’ve spent all day trying to calculate a way to get the base freq and loop length to line up and form a perfect loop but there is too much going on that I don’t understand. For example: a single sample with a positive value followed by 264599 samples with 0 should produce a very audible click and I just can’t get it to play on the Z. This is all rather frustrating.

@Curious: I don’t think adsr works at all. I thought that looping didn’t work as well because it seems like everything except the base_freq is ignored but as I can see now, knobs is interpreted as well.
I’d like to understand why the browser thing doesn’t work on your machine, what browser and version are you using?

Here is another script: it converts sound files of almost any type into a max 6 sec length aif synth sample (only the first 6 seconds of each input file are extracted) to be loaded into OP-Z track 5 - 8. It’s written for Windows and needs Perl, sox (sox.sourceforge.net) and the above TE_Patch.pl script. (Could be easily ported to Linux.)

Save the program text below into a file “op-z.bat” and run like this:
op-z.bat {input file 1} {input file 2} …
(wildcards allowed)

---------------------BEGIN PROGRAM TEXT------------------------------------
@echo off
echo ---------------------------------------------------
echo USAGE: op-z.bat sound.wav sample.mp3 drums*.aif ...
echo ---------------------------------------------------
rem - converts each file argument into normalized, 16-bit, 
rem   44.1kHz, mono, max 6 sec aif file with TE patch to 
rem   be loaded as synth sample into OP-Z (tracks 5-8)
rem - temporary _*.aif files are automatically deleted!
rem - needs Perl, sox (sox.sourceforge.net) and TE_patch.pl
    
for %%f in (%*) do "C:\Program Files (x86)\sox-14-4-2\sox.exe" -R --norm "%%f" -c 1 -b 16 -r 44100 "_%%~nf.aif" trim 0 6
    
for %%f in (_*.aif) do perl TE_patch.pl "%%f" "TE%%~nf.aif"
    
del infiles.txt
for %%f in (%*) do echo %%~nf >> infiles.txt
del outfiles.txt
for %%f in (TE_*.aif) do echo %%~nf >> outfiles.txt
setlocal
echo -------------------------------------------
for /f usebackq %%i in (`grep -cf infiles.txt outfiles.txt`) do <nul set /p var=" %%i aif file(s) with OP-Z patch created"
echo :
echo -------------------------------------------
grep -f infiles.txt outfiles.txt
   
del _*.aif
echo -------------------------------------------
echo deleting temp _*.aif files ... Done!  
echo -------------------------------------------
--------------------END PROGRAM TEXT------------------------------------

Using the “trim” and/or “silence” operators of sox together with the : newfile and : restart keywords one could also create a sox command that converts an input file into as many 6 sec fragments as it is long.

@gero_

I’d like to understand why the browser thing doesn’t work on your machine, what browser and version are you using?

Firefox 64.0 (64-Bit) on Windows7.

@gero_ said:
I’m hard at work on this right now. The second parameter for knobs is the loop start point, the third is the end point. Some of the others probably triggers if there is a loop at all. They’re 15 bit and for some reason, 32767 as end point doesn’t work, so 32766 is the max loop Iength I can get. I’ve spent all day trying to calculate a way to get the base freq and loop length to line up and form a perfect loop but there is too much going on that I don’t understand. For example: a single sample with a positive value followed by 264599 samples with 0 should produce a very audible click and I just can’t get it to play on the Z. This is all rather frustrating.

Hi @gero_ ,

This is what I found out about the “knobs” section on the OP-1 (not in OP-Z):
e.g. “knobs”:[0,5136,16691,32767,24576,20480,16384,8200]

  1. start position in sample, blue dot; all the way to the left: 0% => 0
  2. position of green dot, loop start
  3. position of white dot, loop end
  4. end position of sample, red dot; all the way to the right: 100% => 32767
  5. direction: 12000 forward, 24576 reverse
  6. fractional adjustment via shift green encoder (1/100 1/50 1/10 resolution?)
  7. fractional adjustment via shift white encoder
  8. volume adjustment via shift orange encoder; 0% => 8200

No hint of a switch to turn on or off looping here.

I don’t know (yet) what the OP-Z does with these values.

Thanks for that @Curious! Very helpful. Could you elaborate a bit on 6 or 7? Does it adjust the loop point finely or just the start/end point or maybe both? I’ve not been able to get a perfect loop so far, I’ve tried a lot of things both with math and trial and error. I have a sneaking suspicion that there’s some kind of sample rate change going on in the device.
Another thing I noticed is that the base_freq seems to affect the strange aliasing noise you get when pitching the samples. If I had an OP-1, I’d try to find out what its “preferred”, default base_freq is for samples you create on the device. Maybe this way the aliasing might be gone and the math would check out for the loop points.

Another thing I learned while working on this: the 4 bytes after the APPL before the op-1 are the byte length of the chunk. So I could now change that according to the json making it possible to have users change the base_freq. Higher notes pitched way down still sounded pretty good to me so having the sample 6s but way high would allow for very long notes.

I’ve been fiddling with aif files and the OP-Z for a while, discovered that all the interesting bits are located in the op-1 section of the aif file, such as this one below :

op-1{“adsr”:[512,10746,32767,10000,4000,64,4000,4000],“base_freq”:110.0000000000000,“fx_active”:false,“fx_params”:[8000,8000,8000,8000,8000,8000,8000,8000],“fx_type”:“delay”,“knobs”:[0,0,32767,32767,12000,0,0,11583],“lfo_active”:false,“lfo_params”:[16000,16000,16000,16000,16000,16000,16000,16000],“lfo_type”:“tremolo”,“name”:“20750608_0333”,“octave”:0,“synth_version”:1,“type”:“sampler”}

Indeed, the loop settings are hidden in the knob section. Unfortunately, these are not sample locations, but knob values, and translating knob values into actual sample positions is not straightforward. So, I gave up.

Where one hides the loop on/off switch is still a mystery at my side too…

Yes, the 4 bytes after the APPL id, are the length of that APPL chuck, as described by the AIFF specs.

PS. By fiddling around the knob values, I was able to create a sample that - once loaded in the OP-Z - would leak into the other samples im memory, and sound like a wavetable, going through a bunch of other samples :wink:

i dont think there is a loop switch per say.
but the start / end loop points is where it tells it to loop.

like if the start loop = end loop then it doesn’t loop
(or it does loop a 0 distance so u don’t hear anything)

so essentially looping is always turned on.

having said that, i dont think the Z even handles looping at all right now

The Z handles looping. Here is an example : https://we.tl/t-TMEXIql7uj

@audiosampling The leaking thing always happens when the sample is shorter than 6 seconds. Maybe that’s what happened there?
It’s nice to have a conversation about this in-depth stuff. Maybe some of you would like to check my sample guide for correctness? I’ve already been wrong about the looping thing, and the track names and engine names are wrong. http://gerotakke.de/op-z-sample/op-z-samples.md.html
I plan to correct those but first I’ll make custom base_freq a thing and I haven’t given up on the perfect loops, maybe I’ll just have to correct the fractional adjustments. I’ll let you know!

So after fiddling around with the numbers for 6 and 7 I believe that 7 indeed controls the “nudge” of the loop end point; it’s really hard to tell though. What I can say is that the max value is exactly 17400 . Having it at 17400 still loops but not sample perfect with loop start point being 0, knob 6 being 0 and knob 3 being 32766. Having it at 17401 -> no looping (like having knob 3 at 32767).
Whats strange is that most samples I found had 7 being exactly at 8192 (2^13) so I figured it would be some bitwise settings. Doesn’t seem to be the case.
I’m at loss here. I really want simple oscillator loops but I can’t make sense of this.

@gero_ said:
I’m at loss here. I really want simple oscillator loops but I can’t make sense of this.

My current situation too. Great to know we are a couple of people working on that issue. And about the leaking, the sample was exactly 6s. I happened when I set all knobs settings to zero, if I remember correctly.

Ok, I’ll rant a bit about what I tried so far to see if we can maybe find some other angles to approach this.
I’m basically trying to achieve single cycle waveforms because I thought this would be the easiest approach to looping. Since the sound file has to be 44.1kHz and the length is fixed at 6s, I used a frequency of 441Hz to make the file loop perfectly (having exactly 100 samples per cycle, repeated 2646 times). The loop of course is fine when playing back on the computer.
So with this I tried to get the knobs parameters to play exactly from 0% to 100% – no success, the values just don’t seem to add up to the full sample length. I tried this for knobs[2,3] and now experimented a lot with knobs[6,7].

Then I also tried the other obvious thing: make a sound file with an 6s waveform and put the base_freq to 0.166. (I think I actually started with 1Hz and 6 waveform cycles). This doesn’t work at all, the sample sounds all botched and nowhere near a clean oscillator.

Then my thinking was (and now I’m back to this): If I can’t get the sample to loop to 100% full sample length, maybe I can prepare the sample to fit into something that I can actually loop. So I tried lots of things with different base_freqs, wavelengths, calculating the number of samples per knob increment.
I have no results for these experiments so far but this would be my next angle when I get at this again.

Any thoughts?

Kind of same experiments here : sticking to integer frequencies, e.g. 440Hz … in order to have perfect looping happening every second, for sure, then trying to find the knob parameters to loop at the half of the sample (the actual loop would then happen between 3s-6s). Tried knob to at 16383 and 16384 (half of its range) and the factional adjustments to zero. No luck.

I’m getting tired of mounting/unmouting my OPZ in disk mode. I think I will stick to non-looped samples, and wait that TE comes with an official way to fine tune sample loops. I hope they will do.

And here is a script TE_drums.pl to convert 12 sec aif files into OP-Z drum patches for tracks 1 - 4. Usage like above with the TE_patch.pl script.

my @TE_patch = (65,80,80,76,0,0,4,226,111,112,45,49,123,34,100,114,117,109,95,118,101,114,115,105,111,
110,34,58,50,44,34,100,121,110,97,95,101,110,118,34,58,91,48,44,56,49,57,50,44,48,44,48,44,48,44,48,44,48,44,48,93,44,34,101,110,
100,34,58,91,49,51,52,50,49,55,55,50,55,44,50,54,56,52,51,53,52,53,52,44,52,48,50,54,53,51,49,56,49,44,53,51,54,56,55,48,
57,48,56,44,54,55,49,48,56,56,54,51,53,44,56,48,53,51,48,54,51,54,50,44,57,51,57,53,50,52,48,56,57,44,49,48,55,51,55,52,
49,56,49,54,44,49,50,48,55,57,53,57,53,52,51,44,49,51,52,50,49,55,55,50,55,48,44,49,52,55,54,51,57,52,57,57,55,44,49,54,
49,48,54,49,50,55,50,52,44,49,55,52,52,56,51,48,52,53,49,44,49,56,55,57,48,52,56,49,55,56,44,50,48,49,51,50,54,53,57,48,
53,44,50,49,52,55,52,56,51,54,51,50,44,50,54,56,52,51,53,52,53,53,44,53,51,54,56,55,48,57,49,48,44,56,48,53,51,48,54,51,
54,53,44,49,48,55,51,55,52,49,56,50,48,44,49,51,52,50,49,55,55,50,55,53,44,49,54,49,48,54,49,50,55,51,48,44,49,56,55,57,
48,52,56,49,56,53,44,50,49,52,55,52,56,51,54,52,48,93,44,34,102,120,95,97,99,116,105,118,101,34,58,102,97,108,115,101,44,34,102,120,
95,112,97,114,97,109,115,34,58,91,56,48,48,48,44,56,48,48,48,44,56,48,48,48,44,56,48,48,48,44,56,48,48,48,44,56,48,48,48,44,
56,48,48,48,44,56,48,48,48,93,44,34,102,120,95,116,121,112,101,34,58,34,100,101,108,97,121,34,44,34,108,102,111,95,97,99,116,105,118,101,
34,58,102,97,108,115,101,44,34,108,102,111,95,112,97,114,97,109,115,34,58,91,49,54,48,48,48,44,48,44,48,44,49,54,48,48,48,44,48,44,
48,44,48,44,48,93,44,34,108,102,111,95,116,121,112,101,34,58,34,116,114,101,109,111,108,111,34,44,34,110,97,109,101,34,58,34,112,112,48,48,
56,34,44,34,111,99,116,97,118,101,34,58,48,44,34,112,105,116,99,104,34,58,91,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,
44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,44,48,93,44,34,112,108,97,121,109,111,100,
101,34,58,91,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,
49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,
49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,49,49,57,44,53,
49,49,57,93,44,34,114,101,118,101,114,115,101,34,58,91,49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,44,
49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,
48,44,49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,44,49,50,
48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,44,49,50,48,48,48,93,
44,34,115,116,97,114,116,34,58,91,48,44,49,51,52,50,49,55,55,50,55,44,50,54,56,52,51,53,52,53,52,44,52,48,50,54,53,51,49,56,
49,44,53,51,54,56,55,48,57,48,56,44,54,55,49,48,56,56,54,51,53,44,56,48,53,51,48,54,51,54,50,44,57,51,57,53,50,52,48,56,
57,44,49,48,55,51,55,52,49,56,49,54,44,49,50,48,55,57,53,57,53,52,51,44,49,51,52,50,49,55,55,50,55,48,44,49,52,55,54,51,
57,52,57,57,55,44,49,54,49,48,54,49,50,55,50,52,44,49,55,52,52,56,51,48,52,53,49,44,49,56,55,57,48,52,56,49,55,56,44,50,
48,49,51,50,54,53,57,48,53,44,48,44,50,54,56,52,51,53,52,53,53,44,53,51,54,56,55,48,57,49,48,44,56,48,53,51,48,54,51,54,
53,44,49,48,55,51,55,52,49,56,50,48,44,49,51,52,50,49,55,55,50,55,53,44,49,54,49,48,54,49,50,55,51,48,44,49,56,55,57,48,
52,56,49,56,53,93,44,34,116,121,112,101,34,58,34,100,114,117,109,34,44,34,118,111,108,117,109,101,34,58,91,56,49,57,50,44,56,49,57,50,
44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,
44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,
44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,44,56,49,57,50,93,125,10,83,83,78,68);


my $insert = "";
while (@TE_patch) { $insert = $insert . chr ( shift @TE_patch ) };

open H, $ARGV[0];
binmode H;
my $new = do { local $/;  };
close H;

my $p = -1;
for (my $l = 0; $l < length ($new); $l++) {
    my $d = substr $new, $l, 4;
    if ($d eq "SSND") {
        $p = $l;
    }
} 

if ($p == -1) { print "No sound marker SSND found in $ARGV[0]
"; exit; }

open K, ">", $ARGV[1];
binmode K;
print K (substr $new, 0, $p), $insert, (substr $new, ($p + 4));
close K;

And here is another script: it converts sound files of almost any type into a max 12 sec length aif drum sample (only the first 12 seconds of each input file are extracted) to be loaded into OP-Z track 1 - 4. Again, it’s written for Windows and needs Perl, sox (sox.sourceforge.net) and the above TE_drums.pl script. (Could be easily ported to Linux. I have no experience with Mac OS.)

Save the program text below into a file “op-d.bat” and run like this:
op-d.bat {input file 1} {input file 2} …
(wildcards allowed)

@echo off
echo ---------------------------------------------------
echo USAGE: op-d.bat sound.wav sample.mp3 drums*.aif ...
echo ---------------------------------------------------
rem - converts each file argument into normalized, 16-bit, 
rem   44.1kHz, mono, max 12 sec aif file with TE patch to 
rem   be loaded as drum sample into OP-Z (tracks 1-4)
rem - temporary _*.aif files are automatically deleted!
rem - needs Perl, sox (sox.sourceforge.net) and TE_patch.pl
    
for %%f in (%*) do "C:\Program Files (x86)\sox-14-4-2\sox.exe" -R --norm "%%f" -c 1 -b 16 -r 44100 "_%%~nf.aif" trim 0 12
    
for %%f in (_*.aif) do perl TE_drums.pl "%%f" "TE%%~nf.aif"
    
del infiles.txt
for %%f in (%*) do echo %%~nf >> infiles.txt
del outfiles.txt
for %%f in (TE_*.aif) do echo %%~nf >> outfiles.txt
setlocal
echo -------------------------------------------
for /f usebackq %%i in (`grep -cf infiles.txt outfiles.txt`) do <nul set /p var=" %%i aif file(s) with OP-Z patch created"
echo :
echo -------------------------------------------
grep -f infiles.txt outfiles.txt
   
del _*.aif
echo -------------------------------------------
echo deleting temp _*.aif files ... Done!  
echo -------------------------------------------