Skip to content

Commit

Permalink
#12 Add song navigation view
Browse files Browse the repository at this point in the history
  • Loading branch information
tscz committed Jan 10, 2020
1 parent 427bd58 commit a683e8d
Show file tree
Hide file tree
Showing 14 changed files with 554 additions and 128 deletions.
8 changes: 5 additions & 3 deletions src/components/audioManagement/peaksConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ class PeaksConfig {
let segment: SegmentAddOptions = {
id,
labelText: section.type,
startTime: measures.byId[section.firstMeasure].time,
endTime: measures.byId[section.lastMeasure].time,
startTime: measures.byId[section.measures[0]].time,
endTime:
measures.byId[section.measures[section.measures.length - 1]].time,
color: PeaksConfig.SECTIONTYPE_TO_COLOR.get(section.type),
editable: false
};
Expand Down Expand Up @@ -94,7 +95,8 @@ class PeaksConfig {
[SectionType.SOLO, "#26C6DA"],
[SectionType.OUTRO, "#5C6BC0"],
[SectionType.BRIDGE, "#EC407A"],
[SectionType.VERSE, "#78909C"]
[SectionType.VERSE, "#78909C"],
[SectionType.UNDEFINED, "#ffffff"]
]) as ReadonlyMap<SectionType, string>;
}

Expand Down
4 changes: 2 additions & 2 deletions src/components/contentLayout/contentLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ class ContentLayout extends React.Component<Props> {
render() {
return (
<Grid container spacing={2} alignItems="stretch">
<Grid item xs={10}>
<Grid item xs={9}>
{this.props.topLeft}
</Grid>
<Grid item xs={2}>
<Grid item xs={3}>
{this.props.topRight}
</Grid>
<Grid item xs={12}>
Expand Down
94 changes: 94 additions & 0 deletions src/components/sectionOverview/sectionOverview.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React from "react";
import { Provider } from "react-redux";
import { createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";

import { SectionType, TimeSignatureType } from "../../states/analysisSlice";
import {
createdProject,
initializedProject,
LoadingStatus,
Page
} from "../../states/projectSlice";
import { createRootReducer, PersistedState } from "../../states/store";
import ArrayUtil from "../../util/ArrayUtil";
import SectionOverview from "./sectionOverview";

export default {
title: "Components|SectionOverview",
component: SectionOverview
};

export const Default = () => {
let store = createStore(createRootReducer, composeWithDevTools());

let persistedState: PersistedState = {
analysis: {
audioDuration: 100,
audioSampleRate: 44400,
bpm: 120,
firstMeasureStart: 0,
measures: { allIds: [], byId: {} },
sections: {
allIds: [
"INTRO_0_3",
"VERSE_4_19",
"CHORUS_20_27",
"VERSE_28_43",
"CHORUS_44_51",
"BRIDGE_52_59",
"CHORUS_60_67"
],
byId: {
INTRO_0_3: {
measures: ArrayUtil.range(0, 3),
type: SectionType.INTRO
},
VERSE_4_19: {
measures: ArrayUtil.range(4, 19),
type: SectionType.VERSE
},
CHORUS_20_27: {
measures: ArrayUtil.range(20, 27),
type: SectionType.CHORUS
},
VERSE_28_43: {
measures: ArrayUtil.range(28, 43),
type: SectionType.VERSE
},
CHORUS_44_51: {
measures: ArrayUtil.range(44, 51),
type: SectionType.CHORUS
},
BRIDGE_52_59: {
measures: ArrayUtil.range(52, 59),
type: SectionType.BRIDGE
},
CHORUS_60_67: {
measures: ArrayUtil.range(60, 67),
type: SectionType.CHORUS
}
}
},
timeSignature: TimeSignatureType.FOUR_FOUR
},
project: {
audioUrl: "",
currentPage: Page.DEFAULT,
status: LoadingStatus.NOT_INITIALIZED,
syncFirstMeasureStart: false,
title: "Testproject"
}
};

store.dispatch(createdProject(persistedState));
store.dispatch(
initializedProject({ audioDuration: 240, audioSampleRate: 44400 })
);

return (
<Provider store={store}>
<SectionOverview />
</Provider>
);
};
8 changes: 8 additions & 0 deletions src/components/sectionOverview/sectionOverview.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from "react";

import TestEnvironment from "../../tests/TestEnvironment";
import SectionOverview from "./sectionOverview";

it("renders without crashing", () => {
TestEnvironment.smokeTest(<SectionOverview />);
});
106 changes: 106 additions & 0 deletions src/components/sectionOverview/sectionOverview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Button, ButtonGroup, Grid, Typography } from "@material-ui/core";
import React from "react";
import { connect } from "react-redux";

import { Measure, Section } from "../../states/analysisSlice";
import { ApplicationState, NormalizedObjects } from "../../states/store";
import PeaksConfig from "../audioManagement/peaksConfig";

interface PropsFromState {
sections: NormalizedObjects<Section>;
measures: NormalizedObjects<Measure>;
}

interface PropsFromDispatch {}

interface Props {}

type AllProps = PropsFromState & PropsFromDispatch & Props;

class SectionOverview extends React.Component<AllProps> {
render() {
let { sections, measures } = this.props;

let generateMatrix = (arr: string[], size: number) => {
var res = [];
for (var i = 0; i < arr.length; i = i + size)
res.push(arr.slice(i, i + size));
return res;
};

return (
<>
<Grid container direction="column">
{sections.allIds.map(sectionId => (
<Grid item key={sectionId}>
<Grid container direction="column">
<Grid item>
<Typography variant="caption">
{sections.byId[sectionId].type.toLowerCase()}
</Typography>
</Grid>
<Grid item>
<Grid
container
direction="column"
style={{ marginBottom: "5px" }}
>
{generateMatrix(sections.byId[sectionId].measures, 8).map(
row => (
<ButtonGroup orientation="horizontal" size="small">
{row.map(measureId => {
let measure: Measure = measures.byId[measureId];

return (
<Square
key={measure.id}
value={measure.id!}
bg={
PeaksConfig.SECTIONTYPE_TO_COLOR.get(
sections.byId[sectionId].type
)!
}
></Square>
);
})}
</ButtonGroup>
)
)}
</Grid>
</Grid>
</Grid>
</Grid>
))}
</Grid>
</>
);
}
}

class Square extends React.Component<{
value: string;
bg: string;
}> {
render() {
return (
<Button
{...this.props}
style={{
backgroundColor: this.props.bg
}}
>
{this.props.value}
</Button>
);
}
}

const mapStateToProps = ({ analysis }: ApplicationState) => {
return {
sections: analysis.sections,
measures: analysis.measures
};
};

const mapDispatchToProps = {};
export default connect(mapStateToProps, mapDispatchToProps)(SectionOverview);
51 changes: 45 additions & 6 deletions src/pages/structure/structurePage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IconButton, Tooltip } from "@material-ui/core";
import { IconButton, Popover, Tooltip } from "@material-ui/core";
import LoopIcon from "@material-ui/icons/Loop";
import PauseIcon from "@material-ui/icons/Pause";
import PlayArrowIcon from "@material-ui/icons/PlayArrow";
Expand All @@ -16,6 +16,7 @@ import { LoadingStatus } from "../../states/projectSlice";
import { ApplicationState } from "../../states/store";
import { zoomedIn, zoomedOut } from "../../states/waveSlice";
import StructureView from "../../views/structure/structureView";
import StructureNavigationView from "../../views/structureNavigation/structureNavigationView";
import WaveContainer from "../../views/wave/waveContainer";
import WaveControlView from "../../views/waveControl/waveControlView";

Expand All @@ -35,7 +36,23 @@ interface Props {}

type AllProps = PropsFromState & PropsFromDispatch & Props;

class StructurePage extends React.Component<AllProps> {
interface State {
anchorEl: HTMLButtonElement | null;
}

class StructurePage extends React.Component<AllProps, State> {
state: State = {
anchorEl: null
};

handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
this.setState({ anchorEl: event.currentTarget });
};

handleClose = () => {
this.setState({ anchorEl: null });
};

render() {
Log.info("render", StructurePage.name);
return (
Expand All @@ -61,8 +78,27 @@ class StructurePage extends React.Component<AllProps> {
)}

<WaveformControlButton title="Loop" icon={<LoopIcon />} />
<WaveformControlButton title="Metronome" icon={<TimerIcon />} />

<WaveformControlButton
title="Metronome"
icon={<TimerIcon />}
onClick={e => this.handleClick(e)}
/>
<Popover
style={{ height: "400px" }}
open={Boolean(this.state.anchorEl)}
anchorEl={this.state.anchorEl}
onClose={() => this.handleClose()}
anchorOrigin={{
vertical: "bottom",
horizontal: "left"
}}
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
>
<WaveControlView></WaveControlView>
</Popover>
<WaveformControlButton
title="Zoom in"
icon={<ZoomInIcon />}
Expand All @@ -78,7 +114,10 @@ class StructurePage extends React.Component<AllProps> {
></View>
}
topRight={
<View title="Playback Settings" body={<WaveControlView />}></View>
<View
title="Song Navigation"
body={<StructureNavigationView />}
></View>
}
bottom={<View title="Song Structure" body={<StructureView />}></View>}
></ContentLayout>
Expand All @@ -89,7 +128,7 @@ class StructurePage extends React.Component<AllProps> {
const WaveformControlButton = (props: {
title: string;
icon: ReactElement;
onClick?: () => void;
onClick?: (e?: any) => void;
}) => {
return (
<Tooltip title={props.title}>
Expand Down
19 changes: 7 additions & 12 deletions src/states/analysisSlice.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,32 +104,27 @@ it("can update the rhythm (only time signature)", () => {
it("can add a section", () => {
let section: Section = {
type: SectionType.BRIDGE,
firstMeasure: 0,
lastMeasure: 10
measures: ["0", "1", "2", "3", "4"]
};

let id =
section.type + "_" + section.firstMeasure + "_" + section.lastMeasure;

let state: AnalysisState = reducer(undefined, addedSection(section));

expect(state.sections.allIds).toContainEqual(id);
expect(state.sections.byId[id]).toEqual(section);
expect(state.sections.allIds).toContainEqual("BRIDGE_0_4");
expect(state.sections.byId["BRIDGE_0_4"]).toEqual(section);
});

it("can remove a section", () => {
let section: Section = {
type: SectionType.BRIDGE,
firstMeasure: 0,
lastMeasure: 10
measures: ["0", "1", "2", "3", "4"]
};

let state: AnalysisState = reducer(undefined, addedSection(section));

expect(state.sections.allIds.length).toBe(1);
expect(state.sections.byId["BRIDGE_0_10"]).toEqual(section);
expect(state.sections.byId["BRIDGE_0_4"]).toEqual(section);

state = reducer(state, removedSection("BRIDGE_0_10"));
state = reducer(state, removedSection("BRIDGE_0_4"));
expect(state.sections.allIds.length).toBe(0);
expect(state.sections.byId["BRIDGE_0_10"]).toEqual(undefined);
expect(state.sections.byId["BRIDGE_0_4"]).toEqual(undefined);
});
Loading

0 comments on commit a683e8d

Please sign in to comment.