diff --git a/src/components/audioManagement/audioManagement.tsx b/src/components/audioManagement/audioManagement.tsx index 25f62cc..b285ab4 100644 --- a/src/components/audioManagement/audioManagement.tsx +++ b/src/components/audioManagement/audioManagement.tsx @@ -116,7 +116,9 @@ class AudioManagement extends React.Component { 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); diff --git a/src/components/audioManagement/peaksConfig.ts b/src/components/audioManagement/peaksConfig.ts index c298e52..5a5ed59 100644 --- a/src/components/audioManagement/peaksConfig.ts +++ b/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"; @@ -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.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; } export default PeaksOptions; diff --git a/src/components/sectionSelect/sectionSelect.stories.tsx b/src/components/sectionSelect/sectionSelect.stories.tsx new file mode 100644 index 0000000..83e0cc2 --- /dev/null +++ b/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 = () => ( + (currentSelection = sectionType)} + > +); diff --git a/src/components/sectionSelect/sectionSelect.tsx b/src/components/sectionSelect/sectionSelect.tsx new file mode 100644 index 0000000..9b65baf --- /dev/null +++ b/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 ( + + ); +}; + +export default SectionSelect; diff --git a/src/states/analysisSlice.test.ts b/src/states/analysisSlice.test.ts index 849edd8..3187040 100644 --- a/src/states/analysisSlice.test.ts +++ b/src/states/analysisSlice.test.ts @@ -4,6 +4,7 @@ import reducer, { initialAnalysisState, removedSection, resettedAnalysis, + Section, SectionType, TimeSignatureType, updatedRhythm, @@ -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); }); diff --git a/src/states/analysisSlice.ts b/src/states/analysisSlice.ts index 349c0d9..e12a130 100644 --- a/src/states/analysisSlice.ts +++ b/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"; @@ -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 {} @@ -58,13 +60,34 @@ const analysisSlice = createSlice({ addedSection(state, action: PayloadAction
) { state.sections.push(action.payload); }, - updatedSection(state, action: PayloadAction
) { - 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) { 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 }>) { diff --git a/src/views/structure/structureView.stories.tsx b/src/views/structure/structureView.stories.tsx index 90e6f7c..056fd0b 100644 --- a/src/views/structure/structureView.stories.tsx +++ b/src/views/structure/structureView.stories.tsx @@ -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 }) ); diff --git a/src/views/structure/structureView.tsx b/src/views/structure/structureView.tsx index cdcba8d..d6f0bb5 100644 --- a/src/views/structure/structureView.tsx +++ b/src/views/structure/structureView.tsx @@ -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, @@ -37,6 +38,10 @@ class StructureView extends Component { this.props.addedSection(section); }; + handleUpdatedSection = (before: Section, after: Section) => { + this.props.updatedSection({ before, after }); + }; + handleRemoveSection = (id: string) => { this.props.removedSection(id); }; @@ -53,18 +58,32 @@ class StructureView extends Component { - {this.props.sections.map(section => ( - + {this.props.sections.map((section: Section) => ( + this.handleRemoveSection(section.id!)} + onClick={() => + this.handleRemoveSection( + section.firstMeasure + "-" + section.lastMeasure + ) + } > - {section.type} - {section.startTime} - {section.endTime} + + { + this.handleUpdatedSection(section, { + ...section, + type: sectionType + }); + }} + > + + {section.firstMeasure} + {section.lastMeasure} ))} @@ -72,16 +91,9 @@ class StructureView extends Component { { 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 }); }} >