Skip to content

Commit

Permalink
#12 Add song section selection component
Browse files Browse the repository at this point in the history
  • Loading branch information
tscz committed Dec 31, 2019
1 parent 5037c0b commit 82f1e7c
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 59 deletions.
4 changes: 3 additions & 1 deletion src/components/audioManagement/audioManagement.tsx
Expand Up @@ -116,7 +116,9 @@ class AudioManagement extends React.Component<AllProps> {
private repaintWaveform() {
if (this.props.sections && this.peaks) {
this.peaks.segments.removeAll();
this.peaks.segments.add(this.props.sections);
this.peaks.segments.add(
PeaksOptions.sectionsToSegment(this.props.sections)
);
this.peaks.points.removeAll();
this.peaks.points.add(this.props.measures);
this.peaks.zoom.setZoom(this.props.zoomLevel);
Expand Down
39 changes: 39 additions & 0 deletions src/components/audioManagement/peaksConfig.ts
@@ -1,3 +1,7 @@
import { SegmentAddOptions } from "peaks.js";

import { Section, SectionType } from "../../states/analysisSlice";

export const AUDIO_DOM_ELEMENT = "audio_dom_element";
export const ZOOMVIEW_CONTAINER = "zoomview-container";
export const OVERVIEW_CONTAINER = "overview-container";
Expand Down Expand Up @@ -25,6 +29,41 @@ class PeaksOptions {
zoomLevels: [42] //TODO: Define good initial default
};
};

static sectionsToSegment = (sections: Section[]) => {
let segments: SegmentAddOptions[] = [];
sections.forEach(section => {
let segment: SegmentAddOptions = {
id:
section.type + "_" + section.firstMeasure + "-" + section.lastMeasure,
labelText: section.type,
startTime: PeaksOptions.measureStartToMilliseconds(
section.firstMeasure
),
endTime: PeaksOptions.measureEndToMilliseconds(section.lastMeasure),
color: PeaksOptions.SECTIONTYPE_TO_COLOR.get(section.type),
editable: false
};

segments.push(segment);
});

return segments;
};

static measureStartToMilliseconds = (measureStart: number) => measureStart;
static measureEndToMilliseconds = (measureStart: number) => measureStart;

static SECTIONTYPE_TO_COLOR = new Map<SectionType, string>([
[SectionType.INTRO, "#FF0000"],
[SectionType.VERSE, "#FFFF00"],
[SectionType.PRECHORUS, "#00FF00"],
[SectionType.CHORUS, "#008000"],
[SectionType.SOLO, "#000080"],
[SectionType.OUTRO, "#800080"],
[SectionType.BRIDGE, "#00FFFF"],
[SectionType.VERSE, "#800000"]
]) as ReadonlyMap<SectionType, string>;
}

export default PeaksOptions;
18 changes: 18 additions & 0 deletions src/components/sectionSelect/sectionSelect.stories.tsx
@@ -0,0 +1,18 @@
import React from "react";

import { SectionType } from "../../states/analysisSlice";
import SectionSelect from "./sectionSelect";

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

var currentSelection = SectionType.INTRO;

export const Basic = () => (
<SectionSelect
value={currentSelection}
onChange={sectionType => (currentSelection = sectionType)}
></SectionSelect>
);
43 changes: 43 additions & 0 deletions src/components/sectionSelect/sectionSelect.tsx
@@ -0,0 +1,43 @@
import { MenuItem } from "@material-ui/core";
import Select from "@material-ui/core/Select/Select";
import React, { FunctionComponent } from "react";

import { SectionType } from "../../states/analysisSlice";
import PeaksOptions from "../audioManagement/peaksConfig";

const SectionSelect: FunctionComponent<{
value: SectionType;
onChange: (sectionType: SectionType) => void;
}> = props => {
const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => {
let newSection = event.target.value as SectionType;
props.onChange(newSection);
};

return (
<Select
value={props.value}
onChange={handleChange}
style={{
backgroundColor: PeaksOptions.SECTIONTYPE_TO_COLOR.get(props.value)
}}
>
{Object.values(SectionType).map(sectionType => {
return (
<MenuItem
value={sectionType}
style={{
backgroundColor: PeaksOptions.SECTIONTYPE_TO_COLOR.get(
sectionType
)
}}
>
{sectionType}
</MenuItem>
);
})}
</Select>
);
};

export default SectionSelect;
46 changes: 19 additions & 27 deletions src/states/analysisSlice.test.ts
Expand Up @@ -4,6 +4,7 @@ import reducer, {
initialAnalysisState,
removedSection,
resettedAnalysis,
Section,
SectionType,
TimeSignatureType,
updatedRhythm,
Expand Down Expand Up @@ -121,36 +122,27 @@ it("can update the audio source", () => {
});

it("can add a section", () => {
let state: AnalysisState = reducer(
undefined,
addedSection({
id: "section",
startTime: 0,
endTime: 10,
type: SectionType.BRIDGE
})
);
expect(state.sections).toContainEqual({
id: "section",
startTime: 0,
endTime: 10,
type: SectionType.BRIDGE
});
let section: Section = {
type: SectionType.BRIDGE,
firstMeasure: 0,
lastMeasure: 10
};

let state: AnalysisState = reducer(undefined, addedSection(section));
expect(state.sections).toContainEqual(section);
});

it("can remove a section", () => {
let state: AnalysisState = reducer(
undefined,
addedSection({
id: "section",
startTime: 0,
endTime: 10,
type: SectionType.BRIDGE
})
);
expect(state.sections.length).toBe(1);
let section: Section = {
type: SectionType.BRIDGE,
firstMeasure: 0,
lastMeasure: 10
};

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

expect(state.sections.length).toBe(1);

expect(state2.sections.length).toBe(0);
state = reducer(state, removedSection("BRIDGE_0-10"));
expect(state.sections.length).toBe(0);
});
35 changes: 29 additions & 6 deletions src/states/analysisSlice.ts
@@ -1,5 +1,5 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { PointAddOptions, SegmentAddOptions } from "peaks.js";
import { PointAddOptions } from "peaks.js";

import { PersistedState } from "./store";

Expand All @@ -14,8 +14,10 @@ export interface AnalysisState {
readonly measures: Measure[];
}

export interface Section extends SegmentAddOptions {
export interface Section {
type: SectionType;
firstMeasure: number;
lastMeasure: number;
}

export interface Measure extends PointAddOptions {}
Expand Down Expand Up @@ -58,13 +60,34 @@ const analysisSlice = createSlice({
addedSection(state, action: PayloadAction<Section>) {
state.sections.push(action.payload);
},
updatedSection(state, action: PayloadAction<Section>) {
state.sections.filter(section => section.id !== action.payload.id);
state.sections.push(action.payload);
updatedSection(
state,
action: PayloadAction<{ before: Section; after: Section }>
) {
state.sections = state.sections.filter(
section =>
section.type +
"_" +
section.firstMeasure +
"-" +
section.lastMeasure !==
action.payload.before.type +
"_" +
action.payload.before.firstMeasure +
"-" +
action.payload.before.lastMeasure
);
state.sections.push(action.payload.after);
},
removedSection(state, action: PayloadAction<string>) {
state.sections = state.sections.filter(
section => section.id !== action.payload
section =>
section.type +
"_" +
section.firstMeasure +
"-" +
section.lastMeasure !==
action.payload
);
},
resettedAnalysis(state, action: PayloadAction<{ state?: PersistedState }>) {
Expand Down
15 changes: 6 additions & 9 deletions src/views/structure/structureView.stories.tsx
Expand Up @@ -16,25 +16,22 @@ export const Default = () => {
store.dispatch(
addedSection({
type: SectionType.INTRO,
startTime: 0,
endTime: 1,
id: "intro"
firstMeasure: 0,
lastMeasure: 1
})
);
store.dispatch(
addedSection({
type: SectionType.VERSE,
startTime: 1,
endTime: 2,
id: "verse"
firstMeasure: 1,
lastMeasure: 2
})
);
store.dispatch(
addedSection({
type: SectionType.CHORUS,
startTime: 2,
endTime: 3,
id: "chorus"
firstMeasure: 2,
lastMeasure: 3
})
);

Expand Down
44 changes: 28 additions & 16 deletions src/views/structure/structureView.tsx
Expand Up @@ -11,6 +11,7 @@ import RemoveIcon from "@material-ui/icons/Remove";
import React, { Component } from "react";
import { connect } from "react-redux";

import SectionSelect from "../../components/sectionSelect/sectionSelect";
import {
addedSection,
removedSection,
Expand All @@ -37,6 +38,10 @@ class StructureView extends Component<AllProps> {
this.props.addedSection(section);
};

handleUpdatedSection = (before: Section, after: Section) => {
this.props.updatedSection({ before, after });
};

handleRemoveSection = (id: string) => {
this.props.removedSection(id);
};
Expand All @@ -53,35 +58,42 @@ class StructureView extends Component<AllProps> {
</TableRow>
</TableHead>
<TableBody>
{this.props.sections.map(section => (
<TableRow key={section.id}>
{this.props.sections.map((section: Section) => (
<TableRow key={section.firstMeasure + "-" + section.lastMeasure}>
<TableCell component="th" scope="row">
<IconButton
onClick={() => this.handleRemoveSection(section.id!)}
onClick={() =>
this.handleRemoveSection(
section.firstMeasure + "-" + section.lastMeasure
)
}
>
<RemoveIcon></RemoveIcon>
</IconButton>
</TableCell>
<TableCell>{section.type}</TableCell>
<TableCell>{section.startTime}</TableCell>
<TableCell>{section.endTime}</TableCell>
<TableCell>
<SectionSelect
value={section.type}
onChange={sectionType => {
this.handleUpdatedSection(section, {
...section,
type: sectionType
});
}}
></SectionSelect>
</TableCell>
<TableCell>{section.firstMeasure}</TableCell>
<TableCell>{section.lastMeasure}</TableCell>
</TableRow>
))}
<TableRow key="last">
<TableCell component="th" scope="row">
<IconButton
onClick={() => {
this.handleAddSection({
endTime: Math.random(),
startTime: 34,
type: SectionType.OUTRO,
id:
Math.random()
.toString(36)
.substring(2, 15) +
Math.random()
.toString(36)
.substring(2, 15)
type: SectionType.SOLO,
firstMeasure: 1,
lastMeasure: 4
});
}}
>
Expand Down

0 comments on commit 82f1e7c

Please sign in to comment.