Sonnification javascript only returning a single note - javascript

I have been trying to use a node.js script to turn some data into music. The script is only returning a single note for some reason:
The orignal script on github: https://github.com/wbkd/from-data-to-sound had res.concat(scribble.scale('c', but the threw an error Invalid Scale name.
const scribble = require('scribbletune');
// example data
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1];
const min = Math.min(...data);
const octaves = [...Array(5)].map((d, i) => i + 1); // [1, 2, 3, 4, 5]
// creates array of notes like 'c1', 'd1', 'e1', 'gb1', 'ab1', 'bb1', 'c2', ...
const notes = octaves.reduce((res, octave) =>
res.concat(scribble.scale('c1 major', 'whole tone', octave, false))
, []);
const midiData = scribble.clip({
notes: data.map(value => notes[value - min]),
pattern: 'x',
noteLength: '1/16',
});
// write the MIDI file 🎵🎵🎵
scribble.midi(midiData, 'data-sonification.mid');

From scribbletune doc:
each x implies a note on event
scribbletune docs/core/clip
Since you're passing only 1 'x' as a pattern in scribble.clip, it only plays 1 note. In order for all the notes to be played, you can try something like this:
const midiData = scribble.clip({
notes: data.map(value => notes[value - min]),
- pattern: 'x', // only play 1 note
+ pattern: 'x'.repeat(data.length), // repeat this pattern for each note in data
noteLength: '1/16',
});

Related

How to access the inner array resulting from d3.groups?

I'm struggling to understand how I can use the new d3.group and d3.groups methods. My goal is to add a dropdown menu to my d3.chart. Specifically, when I select an option, the chart displays the data for this particular individual. My problem is that I don't understand how I can access the inner array for the selected individual. For example, if I select bib nr. 5, I want to access this individual's ratio score.
I have been stuck for this problem for a long time. I appreciate your help :)
const data = d3.range(20).map(i => ({
bib: Math.floor(i / 5) + 1,
ratio: -1 + Math.random() * 5,
run: [1, 2, 3, 4, 5][i % 5],
name: ['GIRL1', 'GIRL2', 'GIRL3', 'GIRL4'][Math.floor(i / 5)]
}));
// Now I want to use d3.groups
const skiers = d3.groups(data, d => d.bib)
//Logging this gives me a nested array.
console.log(skiers)
// I want to access the inner array for a particular individual, but how can I accomplish this?
// I have tried to use .map()
console.log(skiers.map(d => d.run)) //This gives me undefines
<script src="https://unpkg.com/d3#6.2.0/dist/d3.min.js"></script>
Use filter() to get all the objects where d[0] equals the bib you're trying to collect;
const data = d3.range(20).map(i => ({
bib: Math.floor(i / 5) + 1,
ratio: -1 + Math.random() * 5,
run: [1, 2, 3, 4, 5][i % 5],
name: ['GIRL1', 'GIRL2', 'GIRL3', 'GIRL4'][Math.floor(i / 5)]
}));
const skiers = d3.groups(data, d => d.bib)
const bibToFind = 3;
// Array with objects
const bibFound = skiers.filter(d => d[0] === bibToFind);
// Objects only
const bibObjects = bibFound[0][1];
console.log(bibFound, bibObjects);
<script src="https://unpkg.com/d3#6.2.0/dist/d3.min.js"></script>
Whe you use .map you're iterating only the first level of nested array
[
[ // "d" is this array in your map function
1,
[
{
"bib": 1,
"ratio": 0.6494187230703661,
"run": 1,
"name": "GIRL1"
},
{
"bib": 1,
"ratio": -0.2740398059491158,
"run": 2,
"name": "GIRL1"
},
{
"bib": 1,
"ratio": 3.3705616486650136,
"run": 3,
"name": "GIRL1"
},
{
"bib": 1,
"ratio": 0.9649181479003297,
"run": 4,
"name": "GIRL1"
},
{
"bib": 1,
"ratio": 1.7259011213023032,
"run": 5,
"name": "GIRL1"
}
]
]
]
const data = d3.range(20).map(i => ({
bib: Math.floor(i / 5) + 1,
ratio: -1 + Math.random() * 5,
run: [1, 2, 3, 4, 5][i % 5],
name: ['GIRL1', 'GIRL2', 'GIRL3', 'GIRL4'][Math.floor(i / 5)]
}));
// Now I want to use d3.groups
const skiers = d3.groups(data, d => d.bib)
//Logging this gives me a nested array.
console.log(skiers[0]) // first level
console.log(skiers[0][1]) // second level take second element
console.log(skiers[0][1][0]) // here you have the first object
// so you can map over the specific level of the nested array
console.log(skiers[0][1].map(d => d.run)) // no mo undefined
//or you can convert the nested array to a simple array if if that what are you looking
console.log("flatmap",skiers.flatMap(d=> d[1]))
// and the acces each run property
console.log("flatmap",skiers.flatMap(d=> d[1]).map(d => d.run))
<script src="https://unpkg.com/d3#6.2.0/dist/d3.min.js"></script>
The only difference between d3.group and d3.groups is that the former returns a Map, while the latter returns an array. Therefore, if you use d3.group, the only thing you'll need to get the array (say bib 1) is:
skiers.get(1);
Here is the demo:
const data = d3.range(20).map(i => ({
bib: Math.floor(i / 5) + 1,
ratio: -1 + Math.random() * 5,
run: [1, 2, 3, 4, 5][i % 5],
name: ['GIRL1', 'GIRL2', 'GIRL3', 'GIRL4'][Math.floor(i / 5)]
}));
// Now I want to use d3.groups
const skiers = d3.group(data, d => d.bib)
const innerArray = skiers.get(1);
console.log(innerArray);
<script src="https://unpkg.com/d3#6.2.0/dist/d3.min.js"></script>

What does it mean to write two arrays together?

const theme = createMuiTheme({
spacing: factor => [0, 4, 8, 16, 32, 64][factor],
});
theme.spacing(2); // = 8
This is a sample code in the Material-UI framework document.
Here is the original link to the code:https://material-ui.com/customization/spacing/
[factor] is not an array or a list, it's to get the element from the [0, 4, 8, 16, 32, 64] as an argument, like in this code
const theme = createMuiTheme({
spacing: factor => [0, 4, 8, 16, 32, 64][factor],
});
theme.spacing(2); // = 8
The spacing is function and the factor is parameter / argument
You only have one array in your code block. If you've used arrays before you should be familiar with how you can access indexes within the array to get particular elements:
// 0 1 2 2 3 4
const arr = [0, 4, 8, 16, 32, 64];
const eight = arr[2];
console.log(eight);
Above, I'm using bracket notation ([2]) which allows you to access a given element within the array. We can rewrite the above code to use the literal array [0, 4, 8, 16, 32, 41] in place of the arr variable like so:
const factor = 2; // make 2 a variable called `factor`
const eight = [0, 4, 8, 16, 32, 64][factor];
console.log(eight);
As you can see, [factor] is being used to get a particular value (using bracket notation) from the array [0, 4, 8, 16, 32, 64].

Problem with Mocha test when try to use the 'return' in the function

I am learning the Mocha test for js but have a weird problem an couldn't figure it out. Any big man can help me:
I do this test case:
it("remove all the number larger than 10", () => {
function filter(arr) {
return arr.filter(number => number < 11);
}
assert.equal(filter([1, 3, 3, 5, 10, 29, 3], [1, 3, 3, 5, 10, 3]));
});
But it returns undefined for that filter function, but when I remove the return keyword, it works fine:
it("remove all the number larger than 10", () => {
function filter(arr) {
arr.filter(number => number < 11);
}
assert.equal(filter([1, 3, 3, 5, 10, 29, 3], [1, 3, 3, 5, 10, 3]));
});
Can anyone can explain it to me?
Thanks
You've got a typo, your close paren ) for filter should be immediately after the first array instead of after both arrays.
Also, to compare arrays use assert.deepEqual instead of assert.equal:
it("remove all the number larger than 10", () => {
function filter(arr) {
return arr.filter(number => number < 11);
}
assert.deepEqual(filter([1, 3, 3, 5, 10, 29, 3]), [1, 3, 3, 5, 10, 3]); // SUCCESS
});
(The reason why it was passing when you removed the return keyword is that filter was being passed both arrays, then returning undefined. assert.equal was only being called with one argument so its second argument was implicitly undefined. Since undefined == undefined your test passed.)

How do I use reduce() to mimic the capabilities of forEach()?

I'm trying to accomplish a task with reduce() that I know how to do with forEach(). Unfortunately I'm not sure how to structure the syntax and I can't find a good example.
Example 1) In this code I use forEach() to insert the word species into the first index of each array.
"use strict";
var creatureArray;
creatureArray = [
['zombie', 30, 1, 'bite', 0, 5],
['skeleton', 10, 2, 'sword', 1, 10],
['orc', 15, 4, 'club', 1, 7]
];
creatureArray.forEach(function(value, index, array) {
array[index].unshift('species');
});
console.log(creatureArray);
Example 2) In this code I try to accomplish something similar using .reduce(). However I know that I'm missing a piece of syntax. I can't figure out how to apply the updated array to the accumulator which is then returned as an object. Thanks so much for any help!
"use strict";
var creatureArray, creatureObject;
creatureArray = [
['zombie', 30, 1, 'bite', 0, 5],
['skeleton', 10, 2, 'sword', 1, 10],
['orc', 15, 4, 'club', 1, 7]
];
creatureObject = creatureArray.reduce(function(accumulator, currentValue, index, array) {
array[index].unshift('species');
//what goes here?
return accumulator;
}, {});
console.log(creatureObject);
Here is how you can do accomplish it.
"use strict";
var creatureArray, creatureObject;
creatureArray = [
['zombie', 30, 1, 'bite', 0, 5],
['skeleton', 10, 2, 'sword', 1, 10],
['orc', 15, 4, 'club', 1, 7]
];
creatureObject = creatureArray.reduce((accumulator, currentValue) => {
return [...accumulator, ['species', ...currentValue]];
}, []);
console.log(creatureObject);
The ... syntax above is called the Spread Operator.
When applied it expands the elements of the array into the new array created by the enclosing [ and ], placing each element from the old array as a top level element into the new array. This results in a flat array instead of a nested array. E.g [[1, 2]] -> [[1, 2]], but [...[1, 2]] -> [1, 2].
This is highly useful because it enables both simple concatenation as well as insertion of top level elements either before or after the expanded array.
Consider:
"use strict";
const names = ['Linus', 'Jane', 'David'];
const withJakeAppended = [...names, 'Jake'];
const withJakePrepended = ['Jake', ...names];
[names, withJakeAppended, withJakePrepended].forEach(xs => console.log(xs));
As you can see, when we spread an Array it is not modified, so the pleasant syntax also enables improved ergonomics for immutable, value oriented programming.
Even better, ... works with Sets and Maps as well. In fact, it works any Iterable object, including ones we can create ourselves.
I might add that using the fourth argument to either Array.prototype.forEach or Array.prototype.forEach is a poor practice that should be avoided.
Note if you need to do this in a browser without ES2015 support, you can write
"use strict";
var creatureArray, creatureObject;
creatureArray = [
['zombie', 30, 1, 'bite', 0, 5],
['skeleton', 10, 2, 'sword', 1, 10],
['orc', 15, 4, 'club', 1, 7]
];
creatureObject = creatureArray.reduce(function (accumulator, currentValue) {
return accumulator.concat([['species'].concat(currentValue)]);
}, []);
console.log(creatureObject);
map is more apropriate in this case:
var creatureArray = [ ['zombie' , 30, 1, 'bite' , 0, 5],
['skeleton', 10, 2, 'sword', 1, 10],
['orc' , 15, 4, 'club' , 1, 7] ]
var creatureObject = creatureArray.map(currentValue => ['species', ...currentValue] )
console.log( JSON.stringify( creatureObject ).replace(/],/g, '],\n ') )

Error parsing array through IF Statement

I need to loop through an entire 2D array (OldTable) to check that Column1 has a value of 1 and Col7 is not empty (null). If the above conditions are true then push the current (i) arrays of elements into newTable.
My snippet of JS is as follow...
var newTable = [];
for (var i=1; i<OldTable.length; i++){
if(OldTable[i][0]==1 && OldTable[i][7]!==null){
newTable.push(OldTable[i]);
}
}
Seems like a fairly straight forward thing to do but currently hitting brick wall with this error...
TypeError: Cannot read property "0" from undefined. (line 80, file
"Code"
I have tried to reduce the if statement to just...
if(OldTable[i][0]==1){
...but still the same error.
I'm able to display the array element just fine using...
Browser.msgBox(OldTable[50][0]);
I'm fairly new to JS so could be a simple silly error someone could point out.
UPDATE: In trying to simplying naming, I've actually made it more difficult with conflicting terminology, so have going through and updated the variable names used.
Your code should work if, as noted in the comment by #Massimo, you change your loop from starting at i=1 to i=0, as shown below. Also, just to whet your appetite for more modern tools within JavaScript, I also include an essentially identical solution to the problem using ES6/ES2015.
var myArray = [
[1, 0, 0, 0, 0, 0, 0, 'foo' ], // should pass
[9, 1, 1, 1, 1, 1, 1, 'foo' ], // should fail
[1, 2, 2, 2, 2, 2, 2, 'foo' ], // should pass
[1, 3, 3, 3, 3, 3, 3, null ], // should fail
[0, 4, 4, 4, 4, 4, 4, null ], // should fail
[1, 5, 5, 5, 5, 5, 5, undefined], // should pass
[1, 6, 6, 6, 6, 6, 6, 'foo' ] // should pass
];
function f1(array) {
var newArray = [];
for (var i = 0; i < array.length; i++) {
if (array[i][0] == 1 && array[i][7] !== null) {
newArray.push(array[i]);
}
}
return newArray;
}
const f2 = array => array.filter(e => e[0] === 1 && e[7] !== null);
console.log(f1(myArray));
console.log(f2(myArray));

Categories

Resources