Start a new topic

Orba 2 Hacking Knowledge Base

image

This forum is intended to share Orba 2 hacking tips amongst the Orba 2 community. NOTE: Please post facts that are well understood & useful. If you have theories to discuss, please start another forum and link to it here.


2 people like this idea

The reason you cannot see your Preset in the App is because it must have a unique uuid. That is the 32 character hexadecimal code (MD5) that follows the name. For example:


1981_ca0f71882db74f11a2ac6a25c418b841.artipreset


There are lots of ways to generate an MD5 code. A simple way is to use an online tool such as here.

You'll need to modify the filename with a new one as well as the uuid property inside the file.


Orba doesn't currently support musical modes. However, this feature has been requested numerous times since Orba 1. There are some hacks to accomplish this, but they have downsides. There are two known methods to do this today:


1. Change the Tuning Entry to that of a Drum preset. For example:

<!-- Change the Bass|Chord|Lead TuningEntry node from -->
  <TuningEntry key="C" name="Major" intervals="P1, M2, M3, P4, P5, M6, M7, P8"
               midiOctave="3" transpositionType="RetainNotes" type="tonal" tuning="60, 62, 64, 65, 67, 69, 71, 72"/>
<!-- To a Drum TuningEntry like this -->
  <TuningEntry key="C" name="Major" intervals="P1, M2, A4, m9, P5, M6, m10, m21"
               midiOctave="2" transpositionType="RetainNotes" type="percussion"
               tuning="36,38,42,49,43,45,51,70"/>

 -> Change the MIDI note numbers to the correct values for the mode you want. The downside here is that you cannot use the Orba 2 App's Tuning features to select a Key or Major/Minor modes.


 -- OR --


2. WARNING: This method is dangerous and doing this incorrectly could brick your Orba. Start your Preset as a Chord and modify the ChordModifierParams such that each of the groups of 4 numbers are the same. For example, a Phrygian mode would be "0,0,0,0;-1,-1,-1,-1;-1,-1,-1,-1;0,0,0,0;0,0,0,0;-1,-1,-1,-1;-1,-1,-1,-1;0,0,0,0"

<ChordModifierParams majorChordList="0, 11, 16, 19; 0, 10, 15, 19; 0, 10, 15, 19; 0, 4, 11, 19; 0, -5, 4, 10; 0, 3, 10, 19; 0, 6, 8, 15; 0, 4, 9, 14; "
                           minorChordList="0, 10, 15, 19; 0, 6, 10, 15; 0, 11, 16, 19; 0, 10, 15, 19; 0, 3, 10, 19; 0, 4, 11, 19; 0, 4, 7, 10; 0, 3, 7, 12; "/>

 -> The downside here is that using a Chord type only allows you to press one pad at a time :(


I believe Artiphon has put some changes in place that are going to make this possible in the future but for now... no other options.


5 people like this

@SubSkybox, thank you for editing my post, I think  we will just leave it here posted from you,  to reduce amount of unnecessary actions.

Meanwhile, I guess that I've got some partial understanding of the drum presets sample mapping.

TL;DR:
 when we push a pad,  sample to play the pad is determined as below:
1) note number is taken from the Tuning
2) we lookup for  SampleDrumPatch element  by searching for the this note in  the noteMidi attributes
3) we take index  attribute of the SampleDrum patch and lookup for the corresponding  sampleMap sample group using this index.
4) choose sample by velocity using velocityThresholds.

Long read:

While decide chain that chooses which sample to play for the Lead/Bass - seems to be

 Pads->Tuning->noteThresholds->velocityThresholds->sampleMap->SampledSound[ ]

and the Chords is doing the same plus, you know, chords stuff, for Drums it is different.


For Drums this chain seems to be 

Pads->Tuning-> SampleDrumPatch[ ] -> velocityThresholds->sampleMap->SampledSound[ ]

(it is simplified version without DrumPatch, CymbalPatch and ShakerPatch which are doing something useful as well, but I am skipping it for now,

because it works without these elements and I have still no clue how do they work)


First of all, Tuning.

Tuning is responsible for the midi note numbers produced, and for drums it seems to be something static, with no octave shifts.

Each number means midi note for pads from 1 to 8.

Usually it will be stuff like 36,38,42,49,43,45,51,70, but for some demonstration purposes lets say it is like below, just the same numbers as pads just doubled.

 tuning="11,22,33,44,55,66,77,88" 

it works too, but will produce a preset which is kind of useless for controlling PC as midi controller sending some rarely used notes..

Here I assign note 11 for pad 1, note 22 for pad 2 and so on.


Also, 39 seems to be hardcoded for bump/shaker event, so it is kind of "11,22,33,44,55,66,77,88"+"39" under the hood. Btw, you even can assign same note to the different pads if you like.


Next element in the chain will be the list of SampleDrumPatch elements, where we determine required one exact  SampleDrumPatch by noteMidi attribute. 


For example, if we push Pad 2, and according to our tuning note is 22,  we are choosing SampleDrumPatch with noteMidi="22". In our case it will be SampleDrumPatch with index=1. This index is vital.    Order of SampleDrumPatch elements seems not to be vital, only index is.


  <SampleDrumPatch index="0" note="0" noteMidi="11" ....

  this one ->  <SampleDrumPatch index="1" note="0" noteMidi="22" ....

  <SampleDrumPatch index="2" note="0" noteMidi="33" ....


(There is also a "note" attribute, but it does not make any sense to me currently, and does nothing. I just suspect they are related to the DrumPatch/CymbalPatch stuff which I had omited.)


Anyway, now we've determined SampleDrumPatch index. In all factory presets, amount of SampleDrumPatches is equal to amount of sample groups in sampleMap and velocityThresholds, and that's not a coincidence.

Each SampleDrumPatch has his own dedcated sample group, determined by SampleDrumPatch's index attribute  (counting from zero).  


This index corresponds to the sample group number, so in case of sample mapping like below,

 sampleMap="[0][1][2][3][4][5][6][7][8]">

we have different sample groups with one sample in each, and if we are searching for the sample group for SampleDrumPatch with index=1, it is the second group from the left - [1]

In case if sample group has more than one sample, looks like the same velocityThresholds mechanics as in Lead presets kicks in, that allows to choose sample within a group based on the velocity.

This is the sample group that is going to be triggered. At least when no DrumPatch/CymbalPatch stuff is in the KitPatch.

Most confusing part is noteThresholds. From one side it seems to be ignored. It works for me fine with

noteThresholds="99,100,101,102,103,104,105,106" setting, where there is completely no meaningful corellation with other elements, so it looks useless. However, it still somehow mandatory to have the correct amount of numbers in the noteThresholds (sample groups amount minus 1 ), and each number should be bigger then the previous, otherwise it does not work properly.



For sure, I still can be mistaken in my theories, at least noteThresholds part is kind of strange, and note= attribute mystery is not solved yet at all.


But that's all I've got for now.


Also attaching an example xml that I used for testing and this post.



4 people like this

 Here is a  bash one-liner to download the original sample packs for bash (works in GIT BASH for Windows,  and  in regular Linux bash, have  no idea about Mac, it might possibly have other implementation of grep and sed)

for i in `wget -O - https://storage.googleapis.com/factory_content_sample_pools/ | grep -oP '\<Key\>.*?\</Key>'  | sed -e 's/<[^<>]*>//g' ` ; do echo --- $i ---;  wget https://storage.googleapis.com/factory_content_sample_pools/$i ; done 

  Downloaded 576Mb of zip files easily.  In the  context of emergency content restoration - it still a problem to determine a mother folder for these packs, but there is at least  unreleased_manifest.xml with a list of sample packs that should go to ArtiphonExt2.


2 people like this

I can confirm it is not working for Mac. It will take slight modifications and I will post another version in the future. For those Windows users without GIT BASH, I also made a PowerShell version.

foreach($zip in ([xml](wget https://storage.googleapis.com/factory_content_sample_pools/).Content).ListBucketResult.Contents.Key){Write-Host "Downloading"$zip; (New-Object net.webclient).Downloadfile("https://storage.googleapis.com/factory_content_sample_pools/"+$zip, $pwd.Path+"\"+$zip);}

Create an empty folder, then hold <SHIFT> and Right-Click the folder and choose "Open PowerShell Window here". When PowerShell opens paste in the one-liner and hit <ENTER>

image

image



2 people like this

Format for the Orba2  factory samples is  mono PCM signed 16-bit little-endian 48000hz.


Using wrong format, like other bits may cause white noise instead of the actual sound.
Using wrong  sample rate hz will cause unexpected pitch shifting.


2 people like this

@David Benton

Thanks for documenting this! I did know some of it, but it was really nice to confirm some of what is known. You are spot on with your Seeker / Listener analogy. There was another user who documented uids/sensors/cc#s for Seekers for Orba 1. I haven't had time yet to check them against Orba 2:


https://github.com/IanHalbwachs/orba-presets/blob/main/All%20Gestures.xml


I believe they simply started unrolling the Base64 stuff so that it was easier to edit and work with in the future:

<!-- tap [note] -->
<!-- tilt [cc1] -->
<!-- vibrato [pitch wheel] -->
<!-- spin [cc 112] -->
<!-- radiate [cc74] -->
<!-- press [channel pressure] -->
<!-- shake note [note 69] -->
<!-- shake cc [cc 2] -->
<!-- move cc [cc 113] -->
<!-- bump [note 39] -->

BE CAREFUL! I wouldn't mess with the Modifier Chains unless you really know what you are doing. I believe this data is written directly to specific memory pointers in Orba firmware space. This can overwrite important parts of the firmware and brick your device. As a rule of thumb do not change the length of any arrays or ModifierChain pointers because the current firmware does not validate this.


2 people like this

After tinkering with the .artipreset format for a while, I've finally written explanation of some internals of the .artipreset, regarding the SamplesSet, which is the core part of preset customization.

As we all know, Orba2 presets are configured by an .artipreset file, which is basically an xml file.

Artipresets that work with samples, at least modern ones, contain an xml element SampleSound that is explained below:

[SampleSet] contains a list of wav samples where each sample is described by SampleSound elements array, and some metadata.

>For example:

<SampledSound sampleIndex="0" name="E1" loopStart="130607" loopEnd="331519" pitch="44.847287" fileName="SF2_Synth-Square_E1_E2_b8dd7e94e9ae3436e93b391003f376f3.wav" subdirectory="SF2_Synth-Square" pool="User"></SampledSound>

  <SampledSound sampleIndex="1" name="G#1" loopStart="107400" loopEnd="287479" pitch="56.50404" fileName="SF2_Synth-Square_G#1_G#2_5ad6c793e97f1cc91d22abb82709eda4.wav" subdirectory="SF2_Synth-Square" pool="User"></SampledSound>

  <SampledSound sampleIndex="2" name="C2" loopStart="106165" loopEnd="330438" pitch="71.19063" fileName="SF2_Synth-Square_C2_C3_876a99e3f4e165a58d2997beb1d94c64.wav" subdirectory="SF2_Synth-Square" pool="User"></SampledSound>

[SampleSound] attributes information:

[sampleIndex] attribute is used in SampleSet sampleMap attribute, for composing "groups" from the samples.

[loopStart and loopEnd] define borders for "loop" part that happens if you hold a note for long enough to produce endless sounds, Orba plays this loop until you release the button. loopStart=0 and loopEnd=0 mean that there is no loop. Units here are not "time" based, these values are indexes of the data frames in the .wav file, or something like that.

[fileName] obviously, defines the wav file name, but with one caveat - it is mandatory to have _{uuid}.wav at the end of the filename. Without it, Orba rejects the sample.

UUID is a unique id of the sample, and each file should have its own uuid. UUID is a 16 symbol string, that contains numbers and lower case letters, like "b8dd7e94e9ae3436e93b391003f376f3" and by coincidence any md5 checksum can be used as UUID, at least if all samples have their own unique uuid.

[pool] means a sample pool subdirectory where sample is stored, both on the device itself and in the local Artiphon profile folder. All factory samplepools are like "AritphonExt1","ArtiphonExt2","ArtiphonBasic". But in our case, we always want to user "User" pool. At least this is a designated location for the user content. One day removal from this samplepool will work, I hope.

[subdirectory] is just a folder name for this specific sample set, and I keep it named the same as preset, but that's not an actual requirement.

[pitch] - This is an interesting one, and describes what is the tuning of the sample in Hz. It DOES NOT seem to be used by Orba2 in the process of deciding which sample it should play (it is defined by sampleMap/noteThresholds/velocityThresholds). Instead it is only used to decide how already chosen sample should be transposed during playback, changing both pitch and a speed of the playback.

For example, let's say, we are playing notes from 58 to 62, and according to sample mapping we are using the same sample for all this range, and its pitch is configured in SampledSound as 261.63.

For midinote 60, this sample would be played with no transformations, as note 60 perfectly matches this frequency. For note 58 the same sample will be played about 1.12 slower and therefore with a lower pitch compared to the original. And for note 62, sample will be played 1.12 times faster and higher.

Also, if negative, pitch attribute works differently. "-1" means that sample is never transposed, is static and sounds the same as in file.

If negative but not equal to -1, it will mean a static pitch correction coefficient. Like "how many times faster this sample is compared to what we want it to be".

For example pitch="-1.08844" seems to be fine as a correction coefficient for 44.1khz no-transpose samples. Basically it says "play this sample 1.08844 times slower"

As soon as Orba uses 48khz for playback, 44.1 khz samples will be played 1.08844 times faster without such compensation. But when pitch="-1.08844" is applied, sample is slowed again back by 1.08844, and in the end you hear sample with the same pitch as it is supposed to be.

Now to the sample mapping and thresholds in SampleSet element. They define how Orba2 decides which sample to play. At this point I should mention that I am not still sure about how it works for drums, it seems to choose samples in some other way, not still sure. But for Lead, Bass, and Chords that's how it seems to work.

Here is a snippet from the SampleSet element .artipreset xml:

<SampleSet name="PanDrum"
  noteThresholds="45,46,48,50,52,53"
  velocityThresholds="[50,90][50,90][50,90][50,90][50,90][50,90][50,90]"
  sampleMap="[0,1,2][3,4,5][6,7,8][9,10,11][12,13,14][15,16,17][18,19,20]">

 Let's start with sampleMap.

[sampleMap] creates something like a groups of samples.

In this case the first group contains samples with "index=0", "index=1", "index=2", second contains indexes 3,4,5, and so on.

Samples in one group are triggered by the same midi note, but which one - will be defined by the incoming note velocity and velocityThresholds.

Which sample group will be used to play will is defined by noteThresholds described later. For now let's pretend that we already know which group is triggered.

[velocityThresholds]

Amount of "groups" in velocityThresholds is the same as in sampleMap, and each group corresponds to the group from sampleMap in the same position.

Each velocityThreshold's "group" defines velocity ranges that are used to choose which sample from the corresponding sampleMap group to play.

velocityThresholds group is defined in a somewhat lazy way, omitting the edge values 0 and 127 of the ranges as they are always the same anyway.

That's why amount of the numbers in velocityThresholds group is always one less than in the corresponding sampleMap group.

For example

       velocityThresholds="[50,90]...

                sampleMap="[0,1,2]...

actually means, that there are three velocity ranges, (0-50), (51-90), and (91-127).

And, combined with the information from the corresponding sampleMap group, we know that sample with index=0 will be played if incoming note velocity is in the 0-50 range, index=1 sample is triggered by velocity 51 to 90, and sample with index=2 will play for velocity above 90.

Also [] means group that contains one single range from 0 to 127, and corresponding sampleMap group will contain only one sample's index, like [0] or [10]

Now for [noteThresholds]

This one defines which sampleMap group will be triggered according to the incoming midi note value.

Basically, it is an another lazy-defined list of ranges, with omitted 0 and 127 values on the edges.

noteThresholds="45,46,48,50,52,53" means that there are the following ranges: (0-45),(46-46),(47-48),(49-50),(51-52),(53-53) and, attention, invisibly assumed (54-127)

this last range towards 127 is not written, and that's why amount of numbers in noteThresholds is always one less than amount of groups in sampleMap and velocityThresholds.

Each range corresponds to the sampleMap groups.

For example, 45 note will use sampleMap group [0,1,2] and note 53 will use sampleMap group [15,16,17], and everything above 53 will trigger group [18,19,20].

Therefore, to conclude, when you play a note with a certain velocity, Orba2 uses noteThresholds to decide which sampleMap group is responsible for this note, and after that uses corresponding velocityThresholds group to decide which particular sample (index) should be played.


2 people like this

@Artiphon @BJG145

Orba 2 should always be recoverable as mentioned above.. However, it is not as BJG145 points out (he even has a video of the boot loop the device is stuck in). I would encourage Artiphon to examine the device that BJG145 is/has sending/sent back. Perhaps changes to future firmware could be made to prevent this from happening. 


1 person likes this

I think I've got it! It appears to be using the Float (IEEE754 Single precision 32-bit) standard and is recorded in reverse (many programs do this to simplify bit-shifting). I converted the curve:


[0,0,0,0],[0,0,0,0],

[113,244,40,70],[113,244,40,70],

[113,244,168,70],[113,244,168,70],

[0,254,255,70],[0,254,255,70]


to


0.0 , 0.0

1.08131103515625E4 , 1.08131103515625E4 

2.1626220703125E4 , 2.1626220703125E4 

3.2767E4, 3.2767E4 (Note this value is the max value for a 16-bit signed int)


..which is completely linear! This can't be a coincidence. I'll need to convert the other curves this week to see what all the curves look like. The first one is here:


image



1 person likes this

Just for your amusement - an example of the weird sounds the Orba can make when you start experimenting.

(Don't listen if you are of a nervous disposition!)

wav

1 person likes this

@Ignis32 - yes, I had thought loop points might have been time based - but I have learnt better since!

I don't want to use existing sound fonts - I was planning on making my own, and I'm not too keen on the idea of having to figure out about them first.

What you say partly supports what I might have first tried which would be to calculate a start point and try an end point by adding a number that would be a multiple of 16 - that should tie in with what you have suggested. So thanks for that.

I have tried looking at Juce documentation for hints about things like this - but it hasn't really helped much! (And I barely understand most of it.)

It will take a bit of time for me to get round to making the samples so I'll see what else I can find in the meantime.


I almost fell into the trap that bricked your Orba, if I recall correctly. I was trying to make a lead preset from a chord preset - I found the eventSource="Modifier 0 0" caused the Orba to crash and reboot. Luckily it didn't do more than that. I deleted the preset, and edited out that entry before restoring it and all was well.


1 person likes this

I think I have come to some understanding of the seekers and the mod entries in the synth patch.

All of this is pure guessing, speculation and hopefully enough logic to make sense. So none of this should be considered to the truly accurate.


The mod entries in the synthPatch relate to the three allowed "effects" for each gesture - which is done by altering parameters in the patch on the go. However, there are 4 main groups of mod entries. The fourth group relates to the global chorus and reverb effects and so don't count as part of the three allowed. Whether this group comes first ie: modSource1, or last ie: modSource4, I'm not certain.( I suspect the existence of a 1_2 etc. or 4_2, whichever, is sometimes redundant.)


The format of these entries is modSource1_1. The first number, I have already covered, the second possibly relates to the Oscillator (1 or 2) and 3 relates to VCA or LFO. Someone who understands synth programming may be able to clarify that last idea. The destination parameters I think relate to the various options listed in the dropdown menu in the Gesture Mapping section of the Orbasynth app. Weight hopefully is obvious - how much it affects the chosen parameter.


Seekers relate simply to the collection of data from sensors and translating the data to the programs functions.


My theory is that inMax and inMin entries relate to data that is coming in (obviously). Whether they define the limits it accepts, or the limits of what it will act on, I don't know. I can see some use of the latter.

outMin and outMax is what the incoming values are translated to for the program to use. Again these limits might define what values can be used by the program or if values are outside that limit they can be adjusted or ignored.

The "seekerType" seems a simple intruction - either seek for Note (the keyboard presses) or Controller (the motion sensors) data. The "controller" parameter remains unclear to me. The mix of numbers and "Pitch Bend" in text in the parameters is understandable perhaps, but Aftertouch in there mystifies me a bit.

In "eventSource", Default Note Source, is presumably the keys - what the "Secondary Note Source" is I don't know - perhaps it's a bump (and/or shake?) which have to be handled differently. (This seems a bit confusing because it sort of duplicates "seekerType". Perhaps seekerType calls an action to collect the data, while eventSource defines how it is used when received.


"triggerSource" and "triggerRule" set what will get the effect going - ie: it could be as soon as a key is pressed, or only when gesture data is incoming, or a gesture indicates that the effect should start.

The "metricSensor" is simply whether data is from the keys or the motion sensor. Perhaps it's just a flag for the program.


"metricSelection" is what set of data is to be exported and relates it to midi usage. However this is for both the Orba itself as well as midi signals to other devices. Since "Shake" doesn't have a midi equivalent this would explain the mismatch in the parameters format. MPE X and Y are like graph axis on the key surface, Z is tilt. (Somehow this doesn't seem quite as certain now than it seemed when I first thought it up.)


"maxRate" I think is about how frequently the sensors are polled. Whether this is a timing such as 20 milliseconds apart or 20 times per second I don't know. I think, however, it may be important as it may force the device to try and process more than it handle, causing glitches in the sound output - hence the need for different values for some situations.


The reason you can't get an effect without both the mod and seeker settings is because if you don't use the data incoming you ain't gonna get the required output.


As ever ,I hope this helps and apologise if it's already known.

 

 

 

 

 

 

 

 

 

 

 

 


1 person likes this

The ModifierEntry lists seem to be unnecessary. I have been trying to create a template for making Orba 2 presets. I have resorted to using a few seeker entry sets that I know are "safe" and choosing (by trial and error) which ones work as I hope with the synthPatch I'm working on. Altering parameters as little as I can. For the moment this seems the best way (for me, at least) to go. The problem with that is that it's hard to understand what each ones does and how it will work with different patches - so it's far from ideal.

But the knowledge you guys are bringing help in understanding it all. I will try and add the little crumbs I find to it as I can.



1 person likes this

I didn't expect it to work, but I couldn't resist trying, and it did work. 


I have been trying to get a two octave range with chromatics as well without using the A/Oct switch. My previous best was by using the harmonics in the preset - but it wasn't convincing. Suddenly I had an idea that I thought might work. Changing octave (or a semitone) is easy using pitch bend (I prefer doing that on the key pads rather by tilt, but both will work ), but I had to find a better way of adding the other half of the puzzle - the solution came by adding duplicate entries of sample files but with different frequencies declared, fooling the Orba  to play them at the frequency I wanted rather than the Orba thought it was doing.

I can now play chromatics effected by the velocity thresholds on the keys, and changing octave for each note depending on the position on the key.


Frankly, it's not ideal in terms of usability - but it does work. More of a proof of concept than good viable plan -  it might be better if I'd used tilt for the octave changes but I have found with that you need to be very nifty indeed to avoid unwanted pitches playing when changing over.


I just thought I would note it here in case anyone is interested!


@subskybox - I took the liberty of making a version of your PanDrum (v9) with adjusted pitches and velocity thresholds which will play better  in tune with other presets I think. It seems to sound a bit different and perhaps less character, but has some nice qualities. I won't share it unless you are OK with that -  it's still much more your work than mine.


1 person likes this

I'm looking at GestureCurveAssignmentsEntry. It contains an attribute called curveAssignmentsData which I believe defines one or more Bezier curves. From observations of all the factory presets, I see that there are only 3 variations used:


Drum:

AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEEAQEBAQE=

Lead & Bass:

AQUFBQUFBQUFAQEBAQEBAQEBAQEBAQEBAQYGBgYGBgYGAQEFAQEBAQEEAQEBAQE=

Chord:

AQUFBQUFBQUFAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEFAQEBAQEEAQEBAQE=


Its important to note that Base64 is as follows:

Base64 is a binary to text encoding scheme that is generally used to transfer content-based messages over the Internet. It works by dividing every three bits of binary data into six bit units. The newly created data is represented in a 64-radix numeral system and as seven-bit ASCII text. Because each bit is divided into two bits, the converted data is 33 percent, or one-third, larger than the original data.


Last year, I built a tool to decode these strings and edit them and copy them back as Base64. I'll use it to have a look to see if I can determine exactly what is defined here.  There are a few different Bezier curve types (i.e. cubic and quadratic). These would often require 8 data points in 2D space but I'm not sure how Artiphon has defined these.


The Drum sounds seem louder or respond to less of a velocity curve so there is less variance in the output volumes. I'll report back if/when I crack their system.   


1 person likes this
Login or Signup to post a comment