Skip to content

Commit

Permalink
Add BPM timing function
Browse files Browse the repository at this point in the history
  • Loading branch information
StarrHelixx committed Oct 1, 2019
1 parent b75ad30 commit 4308958
Show file tree
Hide file tree
Showing 19 changed files with 202 additions and 93 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"gif-info": "^1.0.1",
"imgur": "^0.3.1",
"instagram-private-api": "^1.21.1",
"jsmediatags": "^3.9.2",
"lodash": "^4.17.11",
"oauth": "^0.9.15",
"progressbar.js": "^1.0.1",
Expand Down
1 change: 1 addition & 0 deletions src/declarations.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ declare module 'system-font-families';
declare module 'oauth';
declare module 'imgur';
declare module 'twitter';
declare module 'jsmediatags';
declare module 'electron-google-analytics';
declare module 'electron-default-menu';

Expand Down
27 changes: 26 additions & 1 deletion src/renderer/components/player/AudioControl.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as React from 'react';
import Sound from "react-sound";
import jsmediatags from "jsmediatags";
import Timeout = NodeJS.Timeout;

import {getTimestamp} from "../../data/utils";
import {getTimestamp, urlToPath} from "../../data/utils";
import {TF} from "../../data/const";
import Audio from "../library/Audio";
import SoundTick from "./SoundTick";
Expand All @@ -22,8 +23,10 @@ export default class AudioControl extends React.Component {
audio: Audio,
isPlaying: boolean,
showAll: boolean,
detectBPM: boolean,
scenePaths: Array<any>,
onEditKey(key: string, value: string): void,
onBPM(bpm: number): void,
};

readonly state = {
Expand Down Expand Up @@ -60,6 +63,7 @@ export default class AudioControl extends React.Component {
if (this.props.showAll) {
this.tickLoop(true);
}
this.detectBPM();
}

componentDidUpdate(props: any) {
Expand All @@ -79,6 +83,9 @@ export default class AudioControl extends React.Component {
if (this.props.audio.tick && this.props.audio.tickMode == TF.scene && props.scenePaths && props.scenePaths.length > 0 && props.scenePaths !== this.props.scenePaths) {
this.setState({tick: !this.state.tick});
}
if (this.props.audio.url != audio.url || this.props.detectBPM != props.detectBPM) {
this.detectBPM();
}
this._audio=JSON.stringify(this.props.audio);
}

Expand All @@ -88,6 +95,24 @@ export default class AudioControl extends React.Component {
}
}

detectBPM() {
if (this.props.detectBPM) {
new jsmediatags.Reader(urlToPath(this.props.audio.url))
.setTagsToRead(["TBPM"])
.read({
onSuccess: (data: any) => {
const value = data.tags.TBPM.data;
if (value) {
this.props.onBPM(value);
}
},
onError: (error: any) => {
console.error("Error reading ID3 tags:", error.type, error.info);
}
});
}
}

render() {
const playing = this.state.playing
? (Sound as any).status.PLAYING
Expand Down
25 changes: 10 additions & 15 deletions src/renderer/components/player/ImagePlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,22 +75,9 @@ export default class ImagePlayer extends React.Component {
ref={this.idleTimerRef}>
{(this.props.strobeLayer == SL.middle) && (
<Strobe
pulse={this.props.scene.strobePulse}
opacity={1}
durationTF={this.props.scene.strobeTF}
duration={this.props.scene.strobeTime}
durationMin={this.props.scene.strobeTimeMin}
durationMax={this.props.scene.strobeTimeMax}
sinRate={this.props.scene.strobeSinRate}
delayTF={this.props.scene.strobeDelayTF}
delay={this.props.scene.strobeDelay}
delayMin={this.props.scene.strobeDelayMin}
delayMax={this.props.scene.strobeDelayMax}
delaySinRate={this.props.scene.strobeDelaySinRate}
color={this.props.scene.strobeColor}
timeToNextFrame={this.state.timeToNextFrame}
toggleStrobe={this._toggleStrobe}
/>
timeToNextFrame={this.state.timeToNextFrame}
scene={this.props.scene}/>
)}
<IdleTimer
ref={ref => {return this.idleTimerRef}}
Expand Down Expand Up @@ -617,6 +604,14 @@ export default class ImagePlayer extends React.Component {
timeToNextFrame = 1000;
}
break;
case TF.bpm:
const bpmMulti = this.props.scene.timingBPMMulti > 0 ? this.props.scene.timingBPMMulti : 1 / (-1 * (this.props.scene.timingBPMMulti - 2));
timeToNextFrame = 60000 / (this.props.scene.bpm * bpmMulti);
// If we cannot parse this, default to 1s
if (!timeToNextFrame) {
timeToNextFrame = 1000;
}
break;
}
if (nextImg && nextImg.getAttribute("duration") && timeToNextFrame < parseInt(nextImg.getAttribute("duration"))) {
timeToNextFrame = parseInt(nextImg.getAttribute("duration"));
Expand Down
45 changes: 19 additions & 26 deletions src/renderer/components/player/ImageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -248,21 +248,10 @@ export default class ImageView extends React.Component {
<this.ZoomMoveLayer>
{(this.props.scene && this.props.scene.strobe && this.props.scene.strobeLayer == SL.image) && (
<Strobe
strobeFunction={this.strobeImage.bind(this)}
toggleStrobe={this.props.toggleStrobe}
pulse={this.props.scene.strobePulse}
opacity={1}
timeToNextFrame={this.props.timeToNextFrame}
durationTF={this.props.scene.strobeTF}
duration={this.props.scene.strobeTime}
durationMin={this.props.scene.strobeTimeMin}
durationMax={this.props.scene.strobeTimeMax}
sinRate={this.props.scene.strobeSinRate}
delayTF={this.props.scene.strobeDelayTF}
delay={this.props.scene.strobeDelay}
delayMin={this.props.scene.strobeDelayMin}
delayMax={this.props.scene.strobeDelayMax}
delaySinRate={this.props.scene.strobeDelaySinRate}>
scene={this.props.scene}
strobeFunction={this.strobeImage.bind(this)}>
<animated.div className="ImageView__Image" ref={this.contentRef}/>
</Strobe>
)}
Expand All @@ -274,20 +263,8 @@ export default class ImageView extends React.Component {
<Strobe
className={'m-background'}
toggleStrobe={this.props.toggleStrobe}
pulse={this.props.scene.strobePulse}
opacity={1}
timeToNextFrame={this.props.timeToNextFrame}
durationTF={this.props.scene.strobeTF}
duration={this.props.scene.strobeTime}
durationMin={this.props.scene.strobeTimeMin}
durationMax={this.props.scene.strobeTimeMax}
sinRate={this.props.scene.strobeSinRate}
delayTF={this.props.scene.strobeDelayTF}
delay={this.props.scene.strobeDelay}
delayMin={this.props.scene.strobeDelayMin}
delayMax={this.props.scene.strobeDelayMax}
delaySinRate={this.props.scene.strobeDelaySinRate}
color={this.props.scene.strobeColor}/>
scene={this.props.scene}/>
)}
<animated.div className="ImageView__Background" ref={this.backgroundRef} style={{...backgroundStyle}}/>
</this.FadeLayer>
Expand Down Expand Up @@ -322,6 +299,14 @@ export default class ImageView extends React.Component {
const sinRate = (Math.abs(this.props.scene.fadeSinRate - 100) + 2) * 1000;
fadeDuration = Math.floor(Math.abs(Math.sin(Date.now() / sinRate)) * (this.props.scene.fadeDurationMax - this.props.scene.fadeDurationMin + 1)) + this.props.scene.fadeDurationMin;
break;
case TF.bpm:
const bpmMulti = this.props.scene.fadeBPMMulti > 0 ? this.props.scene.fadeBPMMulti : 1 / (-1 * (this.props.scene.fadeBPMMulti - 2));
fadeDuration = 60000 / (this.props.scene.bpm * bpmMulti);
// If we cannot parse this, default to 1s
if (!fadeDuration) {
fadeDuration = 1000;
}
break;
}
}

Expand Down Expand Up @@ -405,6 +390,14 @@ export default class ImageView extends React.Component {
const sinRate = (Math.abs(this.props.scene.transSinRate - 100) + 2) * 1000;
transDuration = Math.floor(Math.abs(Math.sin(Date.now() / sinRate)) * (this.props.scene.transDurationMax - this.props.scene.transDurationMin + 1)) + this.props.scene.transDurationMin;
break;
case TF.bpm:
const bpmMulti = this.props.scene.transBPMMulti > 0 ? this.props.scene.transBPMMulti : 1 / (-1 * (this.props.scene.transBPMMulti - 2));
transDuration = 60000 / (this.props.scene.bpm * bpmMulti);
// If we cannot parse this, default to 1s
if (!transDuration) {
transDuration = 1000;
}
break;
}
}

Expand Down
16 changes: 2 additions & 14 deletions src/renderer/components/player/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,21 +125,9 @@ export default class Player extends React.Component {
<div className="Player">
{showStrobe && (
<Strobe
pulse={this.props.scene.strobePulse}
opacity={this.props.scene.strobeLayer == SL.bottom ? this.props.scene.strobeOpacity : 1}
durationTF={this.props.scene.strobeTF}
duration={this.props.scene.strobeTime}
durationMin={this.props.scene.strobeTimeMin}
durationMax={this.props.scene.strobeTimeMax}
sinRate={this.props.scene.strobeSinRate}
delayTF={this.props.scene.strobeDelayTF}
delay={this.props.scene.strobeDelay}
delayMin={this.props.scene.strobeDelayMin}
delayMax={this.props.scene.strobeDelayMax}
delaySinRate={this.props.scene.strobeDelaySinRate}
color={this.props.scene.strobeColor}
timeToNextFrame={this.state.timeToNextFrame}
toggleStrobe={this._toggleStrobe}
timeToNextFrame={this.state.timeToNextFrame}
scene={this.props.scene}
/>
)}
{!this.state.hasStarted && !this.state.isEmpty && (
Expand Down
73 changes: 41 additions & 32 deletions src/renderer/components/player/Strobe.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,15 @@
import * as React from 'react';
import {animated, useTransition} from "react-spring";

import {TF} from "../../data/const";
import {SL, TF} from "../../data/const";
import Scene from "../../data/Scene";

export default class Strobe extends React.Component {
readonly props: {
className?: string,
toggleStrobe: boolean,
pulse: boolean,
opacity: number,
timeToNextFrame: number,
durationTF: string,
duration: number,
durationMin: number,
durationMax: number,
sinRate: number,
delayTF: string,
delay: number,
delayMin: number,
delayMax: number,
delaySinRate: number,
color?: string,
scene: Scene,
strobeFunction?: Function,
children?: React.ReactNode,
};
Expand Down Expand Up @@ -49,8 +38,8 @@ export default class Strobe extends React.Component {
},
{
from: {
backgroundColor: this.props.color ? this.props.color : "",
opacity: 1,
backgroundColor: this.props.scene.strobeLayer == SL.image ? "" : this.props.scene.strobeColor,
opacity: this.props.scene.strobeLayer == SL.bottom ? this.props.scene.strobeOpacity : 1,
},
enter: {
opacity: 0,
Expand All @@ -61,7 +50,7 @@ export default class Strobe extends React.Component {
reset: true,
unique: true,
config: {
duration: this.props.duration,
duration: this.props.scene.strobeTime,
},
}
);
Expand All @@ -81,7 +70,7 @@ export default class Strobe extends React.Component {

strobe() {
const duration = this.getDuration();
const delay = this.props.pulse ? this.getDelay() : duration;
const delay = this.props.scene.strobePulse ? this.getDelay() : duration;
this.setState({toggleStrobe: !this.state.toggleStrobe, duration: duration, delay: delay});
if (this.props.strobeFunction) {
this.props.strobeFunction();
Expand All @@ -95,40 +84,52 @@ export default class Strobe extends React.Component {
}

componentDidMount() {
if (this.props.pulse ? this.props.delayTF != TF.scene : this.props.durationTF != TF.scene) {
if (this.props.scene.strobePulse ? this.props.scene.strobeDelayTF != TF.scene : this.props.scene.strobeTF != TF.scene) {
this.strobeLoop();
}
}

componentDidUpdate(props: any) {
if (this.props.durationTF != props.durationTF || this.props.delayTF != props.delayTF || this.props.pulse != props.pulse) {
if (this.props.scene.strobeTF != props.scene.strobeTF || this.props.scene.strobeDelayTF != props.scene.strobeDelayTF || this.props.scene.strobePulse != props.scene.strobePulse) {
clearTimeout(this._strobeTimeout);
if (this.props.pulse ? this.props.delayTF != TF.scene : this.props.durationTF != TF.scene) {
if (this.props.scene.strobePulse ? this.props.scene.strobeDelayTF != TF.scene : this.props.scene.strobeTF != TF.scene) {
this.strobeLoop();
}
}
if ((this.props.pulse ? this.props.delayTF == TF.scene : this.props.durationTF == TF.scene) && this.props.toggleStrobe != props.toggleStrobe) {
if ((this.props.scene.strobePulse ? this.props.scene.strobeDelayTF == TF.scene : this.props.scene.strobeTF == TF.scene) && this.props.toggleStrobe != props.toggleStrobe) {
this.strobe();
}
}

shouldComponentUpdate(props: any, state: any) {
return true;
}

componentWillUnmount() {
clearTimeout(this._strobeTimeout);
this._strobeTimeout = null;
}

getDuration() {
let duration;
switch (this.props.durationTF) {
switch (this.props.scene.strobeTF) {
case TF.constant:
duration = Math.max(this.props.duration, 10);
duration = Math.max(this.props.scene.strobeTime, 10);
break;
case TF.random:
duration = Math.floor(Math.random() * (Math.max(this.props.durationMax, 10) - Math.max(this.props.durationMin, 10) + 1)) + Math.max(this.props.durationMin, 10);
duration = Math.floor(Math.random() * (Math.max(this.props.scene.strobeTimeMax, 10) - Math.max(this.props.scene.strobeTimeMin, 10) + 1)) + Math.max(this.props.scene.strobeTimeMin, 10);
break;
case TF.sin:
const sinRate = (Math.abs(this.props.sinRate - 100) + 2) * 1000;
duration = Math.floor(Math.abs(Math.sin(Date.now() / sinRate)) * (Math.max(this.props.durationMax, 10) - Math.max(this.props.durationMin, 10) + 1)) + Math.max(this.props.durationMin, 10);
const sinRate = (Math.abs(this.props.scene.strobeSinRate - 100) + 2) * 1000;
duration = Math.floor(Math.abs(Math.sin(Date.now() / sinRate)) * (Math.max(this.props.scene.strobeTimeMax, 10) - Math.max(this.props.scene.strobeTimeMin, 10) + 1)) + Math.max(this.props.scene.strobeTimeMin, 10);
break;
case TF.bpm:
const bpmMulti = this.props.scene.strobeBPMMulti > 0 ? this.props.scene.strobeBPMMulti : 1 / (-1 * (this.props.scene.strobeBPMMulti - 2));
duration = 60000 / (this.props.scene.bpm * bpmMulti);
// If we cannot parse this, default to 1s
if (!duration) {
duration = 1000;
}
break;
case TF.scene:
duration = this.props.timeToNextFrame;
Expand All @@ -138,16 +139,24 @@ export default class Strobe extends React.Component {

getDelay() {
let delay;
switch (this.props.delayTF) {
switch (this.props.scene.strobeDelayTF) {
case TF.constant:
delay = this.props.delay;
delay = this.props.scene.strobeDelay;
break;
case TF.random:
delay = Math.floor(Math.random() * (this.props.delayMax - this.props.delayMin + 1)) + this.props.delayMin;
delay = Math.floor(Math.random() * (this.props.scene.strobeDelayMax - this.props.scene.strobeDelayMin + 1)) + this.props.scene.strobeDelayMin;
break;
case TF.sin:
const sinRate = (Math.abs(this.props.delaySinRate - 100) + 2) * 1000;
delay = Math.floor(Math.abs(Math.sin(Date.now() / sinRate)) * (this.props.delayMax - this.props.delayMin + 1)) + this.props.delayMin;
const sinRate = (Math.abs(this.props.scene.strobeDelaySinRate - 100) + 2) * 1000;
delay = Math.floor(Math.abs(Math.sin(Date.now() / sinRate)) * (this.props.scene.strobeDelayMax - this.props.scene.strobeDelayMin + 1)) + this.props.scene.strobeDelayMin;
break;
case TF.bpm:
const bpmMulti = this.props.scene.strobeDelayBPMMulti > 0 ? this.props.scene.strobeDelayBPMMulti : 1 / (-1 * (this.props.scene.strobeDelayBPMMulti - 2));
delay = 60000 / (this.props.scene.bpm * bpmMulti);
// If we cannot parse this, default to 1s
if (!delay) {
delay = 1000;
}
break;
case TF.scene:
delay = this.props.timeToNextFrame;
Expand Down
6 changes: 6 additions & 0 deletions src/renderer/components/sceneDetail/AudioGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,9 @@ export default class AudioGroup extends React.Component {
audio={a}
showAll={this.props.isPlayer}
isPlaying={this.props.isPlaying}
detectBPM={i == 0}
scenePaths={this.props.scenePaths}
onBPM={this.onBPM.bind(this)}
onEditKey={this.onEditKey.bind(this, a.id)}/>
{i != this.props.scene.audios.length - 1 && (
<hr/>
Expand Down Expand Up @@ -86,4 +88,8 @@ export default class AudioGroup extends React.Component {
newAudios.splice(newAudios.map((a) => a.id).indexOf(id), 1);
this.update((s) => {s.audios = newAudios});
}

onBPM(bpm: number) {
this.update((s) => {s.bpm = bpm})
}
}
Loading

0 comments on commit 4308958

Please sign in to comment.