PHP - Map a 16 digit number pattern to binary chunks - php

I am working on a step sequencer program for drum sounds. It takes a 16 bit binary pattern example: '1010010100101001' and then it breaks the binary pattern into chunks like so: 10, 100, 10, 100, 10, 100, 1. It then assigns each chunk a time value based on how many digits. Reason why, is some drum sample sounds ring out longer than the length of 1 beat, so the chunking solves this part. (for example if the beat was 60bpm 1 digit = 1 second) '10' = 2 seconds, '100' = 3 seconds, '1' = seconds. (allowing me to trim the sounds to the proper length in the pattern and concat it into a final wav using ffmpeg) Also 1 = drum hit / 0 = silent hit..... This method works great for my needs.
Now I can make perfect beat loops.... and I want to add a velocity pattern layer on top of this to allow ghost notes / add human feel / dynamics to my drum patterns. I have decided to use a 0,1,2,3,4 value system for the velocity patterns. '0' = 0% volume, '1' = 25% volume, '2' = 50% volume, '3' = 75% volume, and '4' = %100 volume. (0 volume so I can add open hi hat / cymbal crash hard stops that a 0 in binary pattern wouldn't do) So along with the '1111111111111111' pattern you would see a velocity pattern layer, say '4242424242424242' (That velocity pattern alternates 100% hit and 50% hit and sounds good with hi hats / like a real drummer)
Using PHP I am breaking 16 bit binary patterns into an array of chunks. '1001110011110010' would be
['100','1','1','100','1','1','1','100','10']
Now via a loop, I need to map another 16 digit number layer pattern of 0,1,2,3,4 digits to first digit of each chunk.
Example 1:
Velocity Pattern: '4242424242424242'
Binary Pattern: '1001110011110010'
Array = ['100','1','1','100','1','1','1','100','10']
'100' = 4 (1st digit in 4242424242424242 pattern)
'1' = 2 (4th digit in 4242424242424242 pattern)
'1' = 4 (5th digit in 4242424242424242 pattern)
'100' = 2 (6th digit in the 4242424242424242 pattern)
'1' = 4 (9th digit in the 4242424242424242 pattern)
'1' = 2 (10th digit in the 4242424242424242 pattern)
'1' = 4 (11th digit in the 4242424242424242 pattern)
'100' = 2 (12th digit in the 4242424242424242 pattern)
'10' = 4 (15th digit in the 4242424242424242 pattern)
Example 2:
Velocity Pattern: '4242424242424242'
Binary Pattern: '1111111111111111'
Array = ['1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1']
'1' = 4 (n1 digit in 4242424242424242 pattern)
'1' = 2 (n2 digit in 4242424242424242 pattern)
'1' = 4 (n3 digit in 4242424242424242 pattern)
'1' = 2 (n4 digit in 4242424242424242 pattern)
'1' = 4 (n5 digit in 4242424242424242 pattern)
'1' = 2 (n6 digit in 4242424242424242 pattern)
'1' = 4 (n7 digit in 4242424242424242 pattern)
'1' = 2 (n8 digit in 4242424242424242 pattern)
'1' = 4 (n9 digit in 4242424242424242 pattern)
'1' = 2 (n10 digit in 4242424242424242 pattern)
'1' = 4 (n11 digit in 4242424242424242 pattern)
'1' = 2 (n12 digit in 4242424242424242 pattern)
'1' = 4 (n13 digit in 4242424242424242 pattern)
'1' = 2 (n14 digit in 4242424242424242 pattern)
'1' = 4 (n15 digit in 4242424242424242 pattern)
'1' = 2 (n16 digit in 4242424242424242 pattern)
Example 3:
Velocity Pattern: '4231423142314231'
Binary Pattern: '0001000100010001'
Array = ['0','0','0','1000','1000','1000','1']
'0' = 4 (1st digit in 4231423142314231 pattern)
'0' = 2 (2nd digit in 4231423142314231 pattern)
'0' = 3 (3rd digit in 4231423142314231 pattern)
'1000' = 1 (4th digit in 4231423142314231 pattern)
'1000' = 1 (8th digit in 4231423142314231 pattern)
'1000' = 1 (12th digit in 4231423142314231 pattern)
'1' = 1 (16th digit in 4231423142314231 pattern)
The patterns will vary, so I need a method that works even if the pattern starts with 0, ect.
a pattern of 111111111111111 would be easy since each 1 is already split into a group by itself.
I tried using a counter called "$v_count" to map find the position in the pattern but its not working like expected.
$v_count = 0;
$beat_pattern = '1001110011110010';
$velocity_pattern = '4242424242424242';
preg_match_all('/10*|0/', $beat_pattern, $m);
$c_count = count($m, COUNT_RECURSIVE) - 1;
for ($z = 0; $z < $c_count; $z++) {
$z2 = $z;
${"c" . $z} = $m[0][$z];
${"cl" . $z} = strlen($m[0][$z]);
if (${"cl" . $z} == 1 & $m[0][$z] == "0") {
$v_count = $v_count + 1;
echo 'the position of this chunk is: '.$v_count.' in the velocity_pattern<br>';
};
if (${"cl" . $z} == 1 & $m[0][$z] == "1") {
$v_count = $v_count + 1;
echo 'the position of this chunk is: '.$v_count.' in the velocity_pattern<br>';
};
if (${"cl" . $z} > 1) {
if ($z == 1)
{
$v_count = 1;
}
if ($z > 1)
{
$v_count = $v_count + 1;
}
echo ' - the velocity position of this chunk is: '.$v_count.' in the pattern<br>';
$v_count = $v_count + ${"cl" . $z} + 1;
};
}

From the example you've given, it seems that you need the corresponding value from the velocity array and the duration between the 1's in the beat array.
This code first extracts the 1's by splitting it into an array and then filtering out the 0's. So
$beat_pattern = '1001110011110010';
$velocity_pattern = '4242424242424242';
$beat = array_filter(str_split($beat_pattern));
would give in $beat...
Array
(
[0] => 1
[3] => 1
[4] => 1
[5] => 1
[8] => 1
[9] => 1
[10] => 1
[11] => 1
[14] => 1
)
it then takes each entry in turn, works out the length by looking at the next key and subtract the two, also using the index to get the corresponding velocity.
To account for the starting with 0, you can loop up to the first instance of 1 and output the velocity pattern for the same element...
$beat_pattern = '1001110011110010';
$velocity_pattern = '4242424242424242';
$beat = array_filter(str_split($beat_pattern));
$beatKeys = array_keys($beat);
// For the leading 0's
for( $i = 0; $i < $beatKeys[0]; $i++ ) {
echo "1-". $velocity_pattern[$i] . PHP_EOL;
}
for ( $i = 0; $i < count($beatKeys); $i++ ) {
echo ($beatKeys[$i+1] ?? strlen($beat_pattern)) - $beatKeys[$i] . "-".
$velocity_pattern[$beatKeys[$i]] . PHP_EOL;
}
gives (length-velocity)...
3-4
1-2
1-4
3-2
1-4
1-2
1-4
3-2
2-4

Assuming your two input strings:
$binary = '0001000110101001';
$velocity = '4231423142314231';
If you analyse the pattern with a regex, you can obtain all the component parts in one operation, including pauses at the start of the pattern (which are essentially 0% volume beats).
$index = 0;
preg_match_all('/^0+|10*/', $binary, $parts);
foreach ($parts[0] as $part) {
$duration = strlen($part); // How many beats
$volume = $part[0] ? $velocity[$index] : 0; // The corresponding volume number
$index += $duration;
}
To develop this further, it seems to me that it would be practical to produce a proper array of data for the pattern, and you could package up this functionality if you so wanted:
function drumPattern($binary, $velocity) {
$output = [];
$index = 0;
preg_match_all('/^0+|10*/', $binary, $parts);
foreach ($parts[0] as $part) {
$duration = strlen($part);
$output[] = [
'duration' => $duration,
'volume' => $part[0] ? $velocity[$index] : 0
];
$index += $duration;
}
return $output;
}
Example
drumPattern($binary, $velocity);
Produces the following output
Array
(
[0] => Array
(
[duration] => 3
[volume] => 0
)
[1] => Array
(
[duration] => 4
[volume] => 1
)
[2] => Array
(
[duration] => 1
[volume] => 1
)
[3] => Array
(
[duration] => 2
[volume] => 4
)
[4] => Array
(
[duration] => 2
[volume] => 3
)
[5] => Array
(
[duration] => 3
[volume] => 4
)
[6] => Array
(
[duration] => 1
[volume] => 1
)
)

Related

String number approximation

I was thinking about a "number approximation" function that takes an integer and returns a string, similar to the following:
45 => "some"
100 => "1 hundred"
150 => "over 1 hundred"
1,386 => "over 1 thousand"
15,235,742 => "over 15 million"
797,356,264,255 => "over 700 billion"
I was hoping to use it for, for example, saying how many rows in a database table in an approximate manner.
I couldn't think how to describe such a thing so searching for it has been somewhat tricky.
Does any body know of an existing function (preferably in PHP) that does this, or could anybody describe/point to an algo to get me started on rolling my own?
Take a look at this package: http://pear.php.net/package-info.php?package=Numbers_Words
The following code explained in comments would do it
I've given two options. One only with words. The second that one you have exactly said in your answer. The first one is easier because you don't need to preconvert words to numbers again.
<?php
require_once "Numbers/Words.php";
$number = new Numbers_Words();
$input = "797,356,264,255";
$input = str_replace(',', '',$input); // removing the comas
$output = $input[0]; // take first char (7)
$output2 = $input[0].'00'; //7 + appended 00 = 700 (for displaying 700 instead of 'seven hundred')
for ($i = 1; $i<strlen($input); $i++) {
$output .= '0';
}
$words = $number->toWords($output); //seven hundred billion
$output3 = explode(' ', $words);
$word = $output3[count($output3)-1]; // billion
echo "Over ". $words; // Over seven hundred billion
#####################
echo "Over " . $output2 . ' ' . $word; // Over 700 billion
What do you want to do is so subjective. That's why you cannot find any function to do that.
For your algorithm, you can define some strings which will match with patterns. For example: over ** million matches with a number of 8 digits. You can find the firsts 2 digits and replace ** in the string.
Then you can use math function like round, floor, ceil (it depends of what you want), and find the string corresponding to your pattern.
After a little bit of fiddling I have come up with this:
function numberEstimate($number) {
// Check for some special cases.
if ($number < 1) {
return "zero";
} else if ($number< 1000) {
return "less than 1 thousand";
}
// Define the string suffixes.
$sz = array("thousand", "million", "billion", "trillion", "gazillion");
// Calculate.
$factor = floor((strlen($number) - 1) / 3);
$number = floor(($number / pow(1000, $factor)));
$number = floor(($number / pow(10, strlen($number) - 1))) * pow(10, strlen($number) - 1);
return "over ".$number." ".#$sz[$factor - 1];
}
which outputs something like this:
0 => "zero"
1 => "less than 1 thousand"
10 => "less than 1 thousand"
11 => "less than 1 thousand"
56 => "less than 1 thousand"
99 => "less than 1 thousand"
100 => "less than 1 thousand"
101 => "less than 1 thousand"
465 => "less than 1 thousand"
890 => "less than 1 thousand"
999 => "less than 1 thousand"
1,000 => "over 1 thousand"
1,001 => "over 1 thousand"
1,956 => "over 1 thousand"
56,123 => "over 50 thousand"
99,213 => "over 90 thousand"
168,000 => "over 100 thousand"
796,274 => "over 700 thousand"
999,999 => "over 900 thousand"
1,000,000 => "over 1 million"
1,000,001 => "over 1 million"
5,683,886 => "over 5 million"
56,973,083 => "over 50 million"
964,289,851 => "over 900 million"
769,767,890,753 => "over 700 billion"
7,687,647,652,973,863 => "over 7 gazillion"
It may not be the prettiest solution, or the most elegant, but it seems to work and does a good job so I will probably go along with this.
I thank everyone for the pointers and suggestions!

Split 4 digit numbers

I want to split a 4 digit number with 4 digit decimal .
Inputs:
Input 1 : 5546.263
Input 2 : 03739.712 /*(some time may have one zero at first)*/
Result: (array)
Result of input 1 : 0 => 55 , 1 => 46.263
Result of input 2 : 0 => 37 , 1 => 39.712
P.S : Inputs is GPS data and always have 4 digit as number / 3 digit as decimal and some time have zero at first .
You could use the following function:
function splitNum($num) {
$num = ltrim($num, '0');
$part1 = substr($num, 0, 2);
$part2 = substr($num, 2);
return array($part1, $part2);
}
Test case 1:
print_r( splitNum('5546.263') );
Output:
Array
(
[0] => 55
[1] => 46.263
)
Test case 2:
print_r( splitNum('03739.712') );
Output:
Array
(
[0] => 37
[1] => 39.712
)
Demo!
^0*([0-9]{2})([0-9\.]+) should work just fine and do what you want:
$input = '03739.712';
if (preg_match('/^0*([0-9]{2})([0-9\.]+)/', $input, $matches)) {
$result = array((int)$matches[1], (float)$matches[2]);
}
var_dump($result); //array(2) { [0]=> int(37) [1]=> float(39.712) }
Regex autopsy:
^ - the string MUST start here
0* - the character '0' repeated 0 or more times
([0-9]{2}) - a capturing group matching a digit between 0 and 9 repeated exactly 2 times
([0-9\.]+) - a capturing group matching a digit between 0 and 9 OR a period repeated 1 or more times
Optionally you can add $ to the end to specify that "the string MUST end here"
Note: Since we cast to an int in the first match, you can omit the 0* part, but if you plan NOT to cast it, then leave it in.

Is the PHP levenshtein() function buggy?

On this page levenshtein(), I am using the example #1 with following variables:
// input misspelled word
$input = 'htc corporation';
// array of words to check against
$words = array('htc', 'Sprint Nextel', 'Sprint', 'banana', 'orange',
'radish', 'carrot', 'pea', 'bean');
Could someone please tell me why the expected result is carrot rather than htc? Thanks
Because the levenshtein distance from htc corporation is 12 whereas the distance to carrot is only 11.
The levenshtein function calculates how many characters it has to add or replace to get to a certain word, and because htc corporation has 12 extra characters than htc it has to remove 12 to get to just htc. To get to the word carrot from htc corporation it takes 11 changes.
"htc corporation" to "htc" has a distance of 12 (remove " corporation" = 12 characters). "htc corporation" to "carrot" has a distance of no more than 11.
"htc corporation" => "corporation": 4
"corporation" => "corporat": 3
"corporat" => "corrat": 2
"corrat" => "carrat": 1
"carrat" => "carrot": 1
4 + 3 + 2 + 1 + 1 = 11
It looks like what you might be looking for isn't straight-up levenshtein distance, but a "closest substring" match. There's an example implementation of such a thing using a modified Levenshtein algorithm here. Using this algorithm gives scores of:
htc: 0
Sprint Nextel: 11
Sprint: 4
banana: 5
orange: 3
radish: 3
carrot: 3
pea: 2
bean: 3
which recognizes "htc" as an exact substring match and gives it a score of zero. The runner-up, "pea", has a score of two, because you could align it with the "p", the "e", or the "a" in corporation, and then replace the other two characters, etc. When working with this algorithm you should be aware that the score will never be higher than the length of the "needle" string, so shorter strings will generally get lower scores (they're "easier to match").
Levenshtein distance is a string metric for measuring the difference between two sequences. Informally, the Levenshtein distance between two words is the minimum number of single-character edits (insertion, deletion, substitution) required to change one word into the other.
here is a simple analysis
$input = 'htc corporation';
// array of words to check against
$words = array(
'htc',
'Sprint Nextel',
'Sprint',
'banana',
'orange',
'radish',
'carrot',
'pea',
'bean'
);
foreach ( $words as $word ) {
// Check for Intercept
$ic = array_intersect(str_split($input), str_split($word));
printf("%s \t l= %s , s = %s , c = %d \n",$word ,
levenshtein($input, $word),
similar_text($input, $word),
count($ic));
}
Output
htc l= 12 , s = 3 , c = 5
Sprint Nextel l= 14 , s = 3 , c = 8
Sprint l= 12 , s = 1 , c = 7
banana l= 14 , s = 2 , c = 2
orange l= 12 , s = 4 , c = 7
radish l= 12 , s = 3 , c = 5
carrot l= 11 , s = 1 , c = 10
pea l= 13 , s = 2 , c = 2
bean l= 13 , s = 2 , c = 2
It clear htc has a distance of 12 while carrot has 11 if you want htc then Levenshtein alone is not enough .. you need to compare exact word then set priorities

php split integer into smaller parts

i'm working on a project that will need to have everything shown with barcodes, so I've generated 7 numbers for EAN8 algorithm and now have to get these 7 numbers seperately, right now i'm using for the generation
$codeint = mt_rand(1000000, 9999999);
and I need to get this 7 numbers each seperately so I can calculate the checksum for EAN8, how can i split this integer to 7 parts, for example
12345678 to
arr[0]=1
arr[1]=2
arr[2]=3
arr[3]=4
arr[4]=5
arr[5]=6
arr[6]=7
any help would be appreciated..
also I think that I'm becoming crazy :D because I already tried most of the solutions you gave me here before and something is not working like it should work, for example:
$codeint = mt_rand(1000000, 9999999);
echo $codeint."c</br>";
echo $codeint[1];
echo $codeint[2];
echo $codeint[3];
gives me :
9082573c
empty row
empty row
empty row
solved! $codeint = (string)(mt_rand(1000000, 9999999));
Try to use str_split() function:
$var = 1234567;
print_r(str_split($var));
Result:
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
[6] => 7
)
There are two ways to do this, one of which is reasonably unique to PHP:
1) In PHP, you can treat an integer value as a string and then index into the individual digits:
$digits = "$codeint";
// access a digit using intval($digits[3])
2) However, the much more elegant way is to use actual integer division and a little knowledge about mathematical identities of digits, namely in a number 123, each place value is composed of ascending powers of 10, i.e.: 1 * 10^2 + 2 * 10^1 + 3 * 10^0.
Consequently, dividing by powers of 10 will permit you to access each digit in turn.
it's basic math you can divide them in loop by 10
12345678 is 8*10^1 + 7*10^2 + 6*10^3...
the other option is cast it to char array and then just get it as char
Edit
After #HamZa DzCyberDeV suggestion
$string = '12345678';
echo "<pre>"; print_r (str_split($string));
But in mind it comes like below but your suggestion is better one.
If you're getting string from your function then you can use below one
$string = '12345678';
$arr = explode(",", chunk_split($string, 1, ','));
$len = count($arr);
unset($arr[$len-1]);
echo "<pre>";
print_r($arr);
and output is
Array
(
[0] => 1
[1] => 2
[2] => 3
[3] => 4
[4] => 5
[5] => 6
[6] => 7
[7] => 8
)
okay what you can do is
Type cast to string with prefill 0
this is how it works
$sinteger = (string)$integer;
$arrsize = 0 ;
for (i=strlen($sinteger), i == 0 ; i--)
{
arr[$arrsize]=$sinteger[i];
$arrsize++;
}
And then what is left you can prefill with zip.
I am sure you can manage the order reverse or previous. but this is simple approach.

Adjust function for pyramid-like distribution

In this question I got help to write a PHP function which gives a pyramid-like distribution:
function getRandomStrength($min, $max) {
$ln_low = log($min, M_E);
$ln_high = log($max, M_E);
$scale = $ln_high-$ln_low;
$rand = (mt_rand()/mt_getrandmax())*$scale+$ln_low;
$value = round(pow(M_E, $rand), 1);
return $value;
}
getRandomStrenth(1.1, 9.9);
// output could be: 1.4 or 8.3 or 9.8 or 7.2 or 2.9 or ...
When I run 50,000 iterations and check how often the numbers from 1 to 9 appear, I get the following list:
1 » 26%
2 » 19%
3 » 14%
4 » 10%
5 » 9%
6 » 7%
7 » 6%
8 » 6%
9 » 4%
This is what I wanted to have. But now I would like to adjust this function a bit. The smaller values should appear more often and the big values should appear less often - so that I get a list like this:
1 » 28%
2 » 20%
3 » 15%
4 » 11%
5 » 9%
6 » 6%
7 » 5%
8 » 5%
9 » 2%
As you can see, I just need a slight modification. But what can I change so that my function behaves as expected?
I tried several things (e.g. changing the base of the logarithm) but this did not change anything.
You can use pow on the random number.
$rand = pow( mt_rand()/mt_getrandmax(), 1.2 )*$scale+$ln_low;
By playing with the exponent value, you can get less or more small value.
Reducing the $scale of your function by a small (constant) amount seems to generate results pretty close to what you're looking for. You can achieve more accurate results by making this reduction of $scale a function of the randomly generated number from mt_rand(), which would require saving (mt_rand()/mt_getrandmax()) to a variable and performing some additional math on $scale.
Here are my tests, you can run it yourself: http://codepad.viper-7.com/ssblbQ
function getRandomStrength($min, $max)
{
$ln_low = log($min, M_E);
$ln_high = log($max, M_E);
$scale = $ln_high-$ln_low - .05; // Subtract a small constant, vary between .05 and .08
$rand = (mt_rand()/mt_getrandmax())*$scale+$ln_low;
$value = round(pow(M_E, $rand), 1);
return $value;
}
$values = array_fill(1, 9, 0);
for( $i = 0; $i < 50000; $i++)
{
$values[ intval( getRandomStrength(1.1, 9.9)) ]++;
}
for( $i = 1; $i <= 9; $i++)
{
$values[ $i] /= 500; // / 50000 * 100 to get a percent
}
var_dump( $values);
Output
Run #1 - Constant = 0.5
array(9) {
[1] => float(26.626) // Should be 28
[2] => float(19.464) // Should be 20
[3] => float(13.476) // Should be 15
[4] => float(10.41) // Should be 11
[5] => float(8.616) // Should be 9
[6] => float(7.198) // Should be 6
[7] => float(6.258) // Should be 5
[8] => float(5.52) // Should be 5
[9] => float(2.432) // Should be 2
}
Run #2 - Constant = 0.65
array(9) {
[1] => float(26.75) // Should be 28
[2] => float(19.466) // Should be 20
[3] => float(13.872) // Should be 15
[4] => float(10.562) // Should be 11
[5] => float(8.466) // Should be 9
[6] => float(7.222) // Should be 6
[7] => float(6.454) // Should be 5
[8] => float(5.554) // Should be 5
[9] => float(1.654) // Should be 2
}
Run #3 - Constant = 0.70
array(9) {
[1] => float(26.848) // Should be 28
[2] => float(19.476) // Should be 20
[3] => float(13.808) // Should be 15
[4] => float(10.764) // Should be 11
[5] => float(8.67) // Should be 9
[6] => float(7.148) // Should be 6
[7] => float(6.264) // Should be 5
[8] => float(5.576) // Should be 5
[9] => float(1.446) // Should be 2
}
For n in {0..1}, y=(x^n)-1, y will range from 0 to x-1. That curve is then easily mapped from 0 to some max value by multiplying by the range and dividing by (x-1). If you change the value x to something near one, the curve will be nearly linear, and at large values, the curve becomes more like a hockey-stick, but will still fall in the same range.
My initial sample value of three won't be precisely what you expressed, but you can adjust it to get the distribution curve you're looking for.
function getCustomStrength($min, $max, $x_val, $base) {
$logmax = $base-1;
$range = $max-$min;
return (pow($base,$x_val)-1)*($range/($base-1))+$min;
}
function getRandomStrength($min, $max) {
$rand = mt_rand()/mt_getrandmax();
$base = 3.0;
return getCustomStrength($min, $max, $rand, $base);
}
getRandomStrength(1.1, 9.9);

Categories