How to play a melody using Tone.js - tone.js

I have an array of notes like this
const notes = [{note: 'C4', duration: '8n'}, {note: 'D4', duration: '4n'}, {note: 'C4', duration: '2n'}, {note: '', duration: '8n'}] //last element stands for a pause
How do I play a melody based on this array?
And is there a better way to store a melody?
Or maybe should I use another lib for this?
Thank you!

So I've done it this way:
playTab = tab => {
const now = Tone.Time()
let currentTime = 0
tab.forEach(item => {
const {note, duration} = parseNote(item)
if (note !== '') {
this.synth.triggerAttackRelease(note, duration, now + currentTime)
}
currentTime += Tone.Time(duration).toSeconds()
})
}
Is it a good decision? Or should it be done differently?

Related

Why mapTo changes only one time?

I'm making a stopwatch and when I wanna reset the clock for the second time, it is not changed.
On click at the first time, it sets h: 0, m: 0, s: 0. But when click again, it doesn't set h: 0, m: 0, s: 0 and stopwatch goes ahead.
const events$ = merge(
fromEvent(startBtn, 'click').pipe(mapTo({count: true})),
click$.pipe(mapTo({count: false})),
fromEvent(resetBtn, 'click').pipe(mapTo({time: {h: 0, m: 0, s: 0}})) // there is reseting
)
const stopWatch$ = events$.pipe(
startWith({count: false, time: {h: 0, m: 0, s: 0}}),
scan((state, curr) => (Object.assign(Object.assign({}, state), curr)), {}),
switchMap((state) => state.count
? interval(1000)
.pipe(
tap(_ => {
if (state.time.s > 59) {
state.time.s = 0
state.time.m++
}
if (state.time.s > 59) {
state.time.s = 0
state.time.h++
}
const {h, m, s} = state.time
secondsField.innerHTML = s + 1
minuitesField.innerHTML = m
hours.innerHTML = h
state.time.s++
}),
)
: EMPTY)
stopWatch$.subscribe()
The Problem
You're using mutable state and updating it as a side-effect of events being emitted by observable (That's what tap does).
In general, it's a bad idea to create side effects that indirectly alter the stream they're created in. So creating a log or displaying a value are unlikely to cause issues, but mutating an object and then injecting it back the stream is difficult to maintain/scale.
A sort-of-fix:
Create a new object.
// fromEvent(resetBtn, 'click').pipe(mapTo({time: {h: 0, m: 0, s: 0}}))
fromEvent(resetBtn, 'click').pipe(map(_ => ({time: {h: 0, m: 0, s: 0}})))
That should work, though it's admittedly a band-aid solution.
A Pre-fab Solution
Here's a stopwatch I made a while ago. Here's how it works. You create a stopwatch by giving it a control$ observable (I use a Subject called controller in this example).
When control$ emits "START", the stopWatch starts, when it emits "STOP", the stopwatch stops, and when it emits "RESET" the stopwatch sets the counter back to zero. When control$ errors, completes, or emits "END", the stopwatch errors or completes.
function createStopwatch(control$: Observable<string>, interval = 1000): Observable<number>{
return defer(() => {
let toggle: boolean = false;
let count: number = 0;
const ticker = () => {
return timer(0, interval).pipe(
map(x => count++)
)
}
return control$.pipe(
catchError(_ => of("END")),
s => concat(s, of("END")),
filter(control =>
control === "START" ||
control === "STOP" ||
control === "RESET" ||
control === "END"
),
switchMap(control => {
if(control === "START" && !toggle){
toggle = true;
return ticker();
}else if(control === "STOP" && toggle){
toggle = false;
return EMPTY;
}else if(control === "RESET"){
count = 0;
if(toggle){
return ticker();
}
}
return EMPTY;
})
);
});
}
// Adapted to your code :)
const controller = new Subject<string>();
const seconds$ = createStopwatch(controller);
fromEvent(startBtn, 'click').pipe(mapTo("START")).subscribe(controller);
fromEvent(resetBtn, 'click').pipe(mapTo("RESET")).subscribe(controller);
seconds$.subscribe(seconds => {
secondsField.innerHTML = seconds % 60;
minuitesField.innerHTML = Math.floor(seconds / 60) % 60;
hours.innerHTML = Math.floor(seconds / 3600);
});
As a bonus, you can probably see how you might make a button that Stops this timer without resetting it.
Without a Subject
Here's an even more idiomatically reactive way to do this. It makes a control$ for the stopwatch by merging DOM events directly (No Subject in the middle).
This does take away your ability to write something like controller.next("RESET"); to inject your own value into the stream at will. OR controller.complete(); when your app is done with the stopwatch (Though you might do that automatically through some other event instead).
...
// Adapted to your code :)
createStopwatch(merge(
fromEvent(startBtn, 'click').pipe(mapTo("START")),
fromEvent(resetBtn, 'click').pipe(mapTo("RESET"))
)).subscribe(seconds => {
secondsField.innerHTML = seconds % 60;
minuitesField.innerHTML = Math.floor(seconds / 60) % 60;
hours.innerHTML = Math.floor(seconds / 3600);
});

dc.js charting nested data arrays

Problem description
I have a large data array with a structure similar to the following and seek to create a chart that will display the timerecords' changesets by hour that the changeset was created.
[ // array of records
{
id: 1,
name: 'some record',
in_datetime: '2019-10-24T08:15:00.000000',
out_datetime: '2019-10-24T10:15:00.000000',
hours: 2,
tasks: ["some task name", "another task"],
changesets: [ //array of changesets within a record
{
id: 1001,
created_at: '2019-10-24T09:37:00.000000'
},
...
]
},
...
]
No matter how I have tried to create the dimension/write reduction functions I can't get the correct values out of the data table.
const changeReduceAdd = (p, v) => {
v.changesets.forEach(change => {
let cHour = timeBand[change.created_hour]
if (showByChanges) {
p[cHour] = (p[cHour] || 0) + (change.num_changes || 0)
} else {
p[cHour] = (p[cHour] || 0) + 1 //this is 1 changeset
}
})
return p
}
const changeReduceRemove = (p, v) => {
v.changesets.forEach(change => {
let cHour = timeBand[change.created_hour]
if (showByChanges) {
p[cHour] = (p[cHour] || 0) - (change.num_changes || 0)
} else {
p[cHour] = (p[cHour] || 0) - 1 //this is 1 changeset
}
})
return p
}
const changeReduceInit = () => {
return {}
}
//next create the array dimension of changesets by hour
//goal: show changesets or num_changes grouped by their created_hour
let changeDim = ndx.dimension(r => r.changesets.map(c => timeBand[c.created_hour]), true)
let changeGroup = changeDim.group().reduce(changeReduceAdd, changeReduceRemove, changeReduceInit)
let changeChart = dc.barChart('#changeset-hour-chart')
.dimension(changeDim)
.keyAccessor(d => d.key)
.valueAccessor(d => d.value[d.key])
.group(changeGroup)
jsfiddle and debugging notes
The main problem I'm having is I want the changesets/created_hour chart, but in every dimension I have tried, where the keys appear correct, the values are significantly higher than the expected.
The values in the "8AM" category give value 5, when there are really only 3 changesets which I marked created_hour: 8:
There are a lot of solutions to the "tag dimension" problem, and you happen to have chosen two of the best.
Either
the custom reduction, or
the array/tag flag parameter to the dimension constructor
would do the trick.
Combining the two techniques is what got you into trouble. I didn't try to figure what exactly was going on, but you were somehow summing the counts of the hours.
Simple solution: use the built-in tag dimension feature
Use the tag/dimension flag and default reduceCount:
let changeDim = ndx.dimension(r => r.changesets.map(c => timeBand[c.created_hour]), true)
let changeGroup = changeDim.group(); // no reduce, defaults to reduceCount
let changeChart = dc.barChart('#changeset-hour-chart')
.dimension(changeDim)
.keyAccessor(d => d.key)
.valueAccessor(d => d.value) // no [d.key], value is count
.group(changeGroup)
fork of your fiddle
Manual, pre-1.4 groupAll version
You also have a groupAll solution in your code. This solution was necessary before array/tag dimensions were introduced in crossfilter 1.4.
Out of curiosity, I tried enabling it, and it also works once you transform from the groupAll result into group results:
function groupall_map_to_group(groupall) {
return {
all: () => Object.entries(groupall.value())
.map(([key,value])=>({key,value}))
}
}
let changeGroup = ndx.groupAll().reduce(changeReduceAdd, changeReduceRemove, changeReduceInit)
let changeChart = dc.barChart('#changeset-hour-chart')
.dimension({}) // filtering not implemented
.keyAccessor(d => d.key)
.valueAccessor(d => d.value) // [d.key]
.group(groupall_map_to_group(changeGroup))
.x(dc.d3.scaleBand().domain(timeBand))
.xUnits(dc.units.ordinal)
.elasticY(true)
.render()
crossfilter 1.3 version

How to make this RxJs code more elegant? The code recording mouse hover time on a specific area

I want to record mouse hover time on a specific area such as a 'div' container box area , by using RxJs.
const element = document.querySelector('#some-div');
let totalHoverTime = 0;
const INTERVAL = 100;
const mousePos = {
x: -1,
y: -1
};
const accumulate = () => {
const x = mousePos.x;
const y = mousePos.y;
const divx1 = element.offsetLeft;
const divy1 = element.offsetTop;
const divx2 = element.offsetLeft + element.offsetWidth;
const divy2 = element.offsetTop + element.offsetHeight;
// out of area
if (x < divx1 || x > divx2 || y < divy1 || y > divy2) {
console.log('out')
} else {
// in area
console.log('in')
totalHoverTime += INTERVAL;
}
};
const accumulateTimer = rx.interval(INTERVAL);
accumulateTimer.subscribe(() => {
accumulate();
});
rx
.fromEvent(element, 'mousemove')
.pipe(rxOp.debounce(() => rx.timer(INTERVAL)))
.subscribe((e: MouseEvent) => {
mousePos.x = e.clientX;
mousePos.y = e.clientY;
});
I'm not very familiar with rxjs, I think this code may can be more elegant to implement.
Optimized code
Thank you very much for your answers. #hugo #der_berni
const element = document.body;
const INTERVAL = 2000;
const withinBounds = ({ x, y }: { x: number; y: number }) => {
const divx1 = element.offsetLeft;
const divy1 = element.offsetTop;
const divx2 = element.offsetLeft + element.offsetWidth;
const divy2 = element.offsetTop + element.offsetHeight;
const outOfBounds = x < divx1 || x > divx2 || y < divy1 || y > divy2;
if (outOfBounds) {
// out of area
console.log('out');
} else {
// in area
console.log('in');
}
return !outOfBounds;
};
const mousePositions = rx
.fromEvent(document, 'mousemove')
.pipe(rxOp.throttleTime(200))
.pipe(rxOp.map((e: MouseEvent) => ({ x: e.pageX, y: e.pageY })));
const mousePositionIsValid = mousePositions
.pipe(rxOp.map(withinBounds))
.pipe(rxOp.distinctUntilChanged());
const hoverTimer = mousePositionIsValid.pipe(rxOp.switchMap(valid => (valid ? accumulateTimer : rx.empty())));
const totalHoverTime = hoverTimer.pipe(rxOp.scan((x, y) => x + INTERVAL, -500)); // The first time mouse moves in, this will be triggered once, so it is set to -500, and the first time it comes in is 0ms.
totalHoverTime.subscribe(hoverTime => {
console.log('totalHoverTime is:', hoverTime);
});
Finally, I found that I still need to use mousemove event combined timer to implement this function. When the mouse is already hovering above the div on page load, the mouseenter event will never triggerd in my page seemly. Maybe only in jsfiddle can be no problem.
I' also only started using RxJS recently, so there might be a better way to solve your problem.
However, a huge improvement over your approach would already be to chain the observables and use the switchMap operator. One thing to keep in mind when working with rxjs is, that you want to avoid manual subscriptions, because you will have to keep track of them and unsubscribe yourself to prevent leaks. When using operators like switchMap, these keep track of the subscriptions to inner observables, and also automatically unsubscribe.
Following code snippet should solve your problem:
Rx.Observable.fromEvent(element, 'mouseenter') // returns Observable<Event>
.map(() => Date.now()) // transform to Observable<number>
.switchMap((startTime) => { // switches to new inner observable
return Rx.Observable.fromEvent(button, 'mouseleave')
// When the observable from mouseleave emmits, calculate the hover time
.map(() => Date.now() - startTime);
})
.subscribe((hoverTime) => {console.log(hoverTime)});
If you want to try it out, see this jsFiddle: https://jsfiddle.net/derberni/hLgw1yvj/3/
EDIT:
Even if your div is very large, and the mouse might never leave it and trigger the mouseleave event, this can be solved with rxjs. You just have to change when the observable emits, and for how long you let it emit before you complete it. The WHEN can be adapted, so that it emits in a set interval, and the UNTIL can be set with the rxjs function takeUntil. takeUntil receives an observable as an argument, and takes values from the source observable, until the 'argument' observable emits.
Check out this code and fiddle, which updates the hover time in 1s steps and when the mouseleave event triggers: https://jsfiddle.net/derberni/3cky0g4e/
let div = document.querySelector('.hover-target');
let text = document.querySelector('.hover-time');
Rx.Observable.fromEvent(div, 'mouseenter')
.map(() => Date.now())
.switchMap((startTime) => {
return Rx.Observable.merge(
Rx.Observable.interval(1000),
Rx.Observable.fromEvent(div, 'mouseleave')
)
.takeUntil(Rx.Observable.fromEvent(div, 'mouseleave'))
.map(() => Date.now() - startTime);
})
//.takeUntil(Rx.Observable.fromEvent(div, 'mouseleave'))
.subscribe((hoverTime) => {
text.innerHTML = "Hover time: " + hoverTime + "ms"
});
At least in the fiddle this works also when the mouse is already hovering above the div on page load, because then the mouseenter event is also triggered.
Point-free
The simplest thing: replace (x => f(x)) with simply f. It's equivalent and will read better in most cases. This:
accumulateTimer.subscribe(() => {
accumulate();
});
Becomes:
accumulateTimer.subscribe(accumulate);
Fat functions:
The accumulate function could be broken down into:
const accumulate = () => {
const x = mousePos.x;
const y = mousePos.y;
if (withinBounds(x, y)) {
totalHoverTime += INTERVAL;
}
};
const withinBounds = ({x, y}) => {
const divx1 = element.offsetLeft;
const divy1 = element.offsetTop;
const divx2 = element.offsetLeft + element.offsetWidth;
const divy2 = element.offsetTop + element.offsetHeight;
const outOfBounds = x < divx1 || x > divx2 || y < divy1 || y > divy2;
if (outOfBounds) {
// out of area
console.log('out')
} else {
// in area
console.log('in')
}
return !outOfBounds;
};
See how we separated withinBounds which is pretty big but performs a simple definite task, purely functionally (no side-effect, one input gives the same output) -- ignoring the debug calls that is. Now we don't have to think so hard about it and we can focus on accumulate.
Avoid side-effects & compose
The most glaring issue is the whole loop relying on a side effect on mousePos:
const mousePositions = rx
.fromEvent(element, 'mousemove')
.pipe(rxOp.debounce(() => rx.timer(INTERVAL)))
//.subscribe((e: MouseEvent) => {
// mousePos.x = e.clientX;
// mousePos.y = e.clientY;
//});
.map((e: MouseEvent) => ({ x: e.clientX, y: e.clientY )));
Don't subscribe and save the value, it breaks the idea of flow behind rxjs. Use the return value, Luke. More specifically, pipe further to refine it until you reach the desired data. Above, we have a stream that emits the mouse positions alone.
// Will emit true when the mouse enters and false when it leaves:
const mousePositionIsValid = mousePositions
.map(withinBounds)
.distinctUntilChanged();
// Fires every INTERVAL, only when mouse is within bounds:
const hoverTimer = mousePositionIsValid
.switchMap(valid => valid ? accumulateTimer : rx.empty())
(edited with switchMap as suggested by #der_berni)
You wrote a function named "accumulate". Whenever you say "accumulate", reduce (and the likes) should come to mind. Reduce emits a single aggregate value when the stream completes. Here we use scan to get a new updated value each time the underlying stream emits:
// For each element produced by hoverTimer, add INTERVAL
const totalHoverTime = hoverTimer.scan((x, y) => x + INTERVAL, 0);
Note that it doesn't add to global each time, but every value it emits is the previous one + INTERVAL. So you can subscribe to that to get your total hover time.

BufferTime with leading option

I have some events that I'd like to buffer but I'd like to buffer only after the first element.
[------bufferTime------]
Input over time:
[1, 2, 3, -------------|---4, 5, 6 ----------------]
Output over time:
[1]-----------------[2,3]---[4]------------------[5,6]
is there a way to do this?
I think this can be solved by dividing your stream into two, firstValue$ and afterFirstValue$, and then merging them.
import { merge } from 'rxjs';
import { take, skip, bufferTime } from 'rxjs/operators';
...
firstValue$ = source$.pipe(
take(1)
);
afterFirstValue$ = source$.pipe(
skip(1),
bufferTime(5000)
);
merge(firstValue$, afterFirstValue$)
.subscribe(result => {
// Do something
});
Answer to follow up question concerning subject
So I have done it so that the original source is a subject here. It is not exactly how you described it, but I think maybe this is what you want.
import { merge, Subject } from 'rxjs';
import { take, skip, bufferTime } from 'rxjs/operators';
import { Source } from 'webpack-sources';
...
source$ = new Subject();
firstValue$ = source$.pipe(
take(1)
);
afterFirstValue$ = source$.pipe(
skip(1),
bufferTime(5000)
);
merge(firstValue$, afterFirstValue$)
.subscribe(result => {
// Do something
});
source$.next(1);
source$.next(1);
source$.next(1);
You can use multicast to split the stream into two and just pass the first value through.
import { concat, Subject } from “rxjs”;
import { multicast, take, bufferCount } from “rxjs/operators”;
source.pipe(
multicast(
new Subject(),
s => concat(
s.pipe(take(1)),
s.pipe(bufferCount(X)),
)
),
);
I got really good answers that enlightened my view of the problem and made me come up with the real thing that I was needing, that was something like this:
function getLeadingBufferSubject (bufferTimeArg) {
const source = new Subject()
const result = new Subject()
let didOutputLeading = false
const buffered$ = source
.pipe(bufferTime(bufferTimeArg))
.pipe(filter(ar => ar.length > 0))
.pipe(map(ar => [...new Set(ar)]))
buffered$.subscribe(v => {
didOutputLeading = false
const slicedArray = v.slice(1)
// emits buffered values (except the first) and set flag to false
if (.length > 0) result.next(v.slice(1))
})
// emits first value if buffer is empty
source.subscribe(v => {
if (!didOutputLeading) {
didOutputLeading = true
result.next(v)
}
})
// call .next(value) on "source"
// subscribe for results on "result"
return {
source,
result
}
}
I had the same problem and after playing around with it, I found this additional solution:
source$.pipe(
buffer(source$.pipe(
throttleTime(bufferTime, asyncScheduler, {leading: true, trailing: true}),
delay(10) // <-- This here bugs me like crazy though!
)
)
Because throttle already features a leading option, you can just use it to trigger buffer emits manually.
I would really like to get rid of that delay here though. This is necessary because the inner observable is triggered first causing the buffer to emit prematurely.

I want change scrollview rolling speed in react native

Now I use interval make it come true, but it is very incoherence.
If I can just change the method (scroll) speed, it well be nice.
this.interval = setInterval(()=>{
if(!_scroll){
_this.interval && clearInterval(_this.interval);
}
if(totalWide+ScreenWidth >= width ){
_scroll.scrollWithoutAnimationTo();
totalWide=0;
i=0;
}else{
_scroll.scrollTo({x:eachWide*i,animate:true});
totalWide = totalWide + eachWide;
i= i+1;
}
},250)
use decelerationRate property of ScrollView
<ScrollView decelerationRate={0.5}>
</ScrollView>
I got this working by having setInterval call a function(in which you define the logic or the pace at which the scroll should move).
this.interval= setInterval(this.scrollwithSpeed, 100); // Set the function to this timer
scrollwithSpeed() {
position = this.state.currentPosition + x; // x decides the speed and
currentPosition is set to 0 initially.
this.scrollObject.scrollTo(
{ y: position, animated: true }
);
this.setState({ currentPosition: position });
}
Make sure you call clearInterval(this.interval) after it is done.
I would suggest to attach to js requestAnimationFrame (from how far I know it is supported in React Native).
Bellow example will scroll linearly from top to bottom. If You need to scoll to different offset just change distance variable.
startingPoint variable is redundant in scrolling from top to bottom but will stay in example.
scroll() {
if (this.scrollAnimationFrame) {
cancelAnimationFrame(this.scrollAnimationFrame);
}
this.listRef.scrollToOffset({offset: 0, animated: false}); // remove if You don't start scroll from top
const duration = this.scrollTime,
startingPoint = 0, // change if You don't start scroll from top
distance = Scrolling.LINE_HEIGHT * Scrolling.ITEMS_COUNT;
let startTimestamp, progress;
const frameCallback = (timestamp) => {
if (!startTimestamp) {
startTimestamp = timestamp;
}
progress = timestamp - startTimestamp;
this.listRef.scrollToOffset({
offset: distance * (progress / duration) + startingPoint,
animated: false,
});
if (progress < duration) {
this.scrollAnimationFrame = requestAnimationFrame(frameCallback);
}
};
this.scrollAnimationFrame = requestAnimationFrame(frameCallback);
}
You can use reanimated to make it work.
const offsetY = useSharedValue(0);
const animatedProps = useAnimatedProps<FlatListProps<unknown>>(() => {
return {
contentOffset: {
x: 0,
y: offsetY.value,
},
};
});
const handleScroll = () => {
offsetY.value = withTiming(targetIndex * CARD_HEIGHT, {
duration: YOUR_DURATION_HERE,
});
}
return <Animated.FlatList animatedProps={animatedProps} ... />

Resources