Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Start from specific step position or loop range #72

Merged
merged 9 commits into from
Feb 6, 2022
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
]
},
"dependencies": {
"@brandongregoryscott/reactronica": "0.8.1",
"@octokit/rest": "18.12.0",
"@supabase/supabase-js": "1.24.0",
"evergreen-ui": "6.7.1",
Expand All @@ -29,7 +30,6 @@
"react-router-config": "5.1.1",
"react-router-dom": "5.3.0",
"react-scripts": "4.0.3",
"@brandongregoryscott/reactronica": "0.7.1",
"rooks": "5.7.2",
"slugify": "1.6.0",
"tone": "14.7.77",
Expand Down
4 changes: 3 additions & 1 deletion src/components/pages/workstation-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { DraggableTrackList } from "components/tracks/track-list/draggable-track
import { SongComposition } from "components/song-composition/song-composition";
import { useListInstruments } from "utils/hooks/domain/instruments/use-list-instruments";
import { List } from "immutable";
import { TrackTime } from "components/tracks/track-time/track-time";

interface WorkstationPageProps extends RouteProps {}

Expand All @@ -50,7 +51,7 @@ const WorkstationPage: React.FC<WorkstationPageProps> = (
props: WorkstationPageProps
) => {
const { user } = useCurrentUser();
const { setState } = useWorkstationState();
const { state, setState } = useWorkstationState();
const {
state: { isPlaying },
} = useReactronicaState();
Expand Down Expand Up @@ -167,6 +168,7 @@ const WorkstationPage: React.FC<WorkstationPageProps> = (
{renderControls && (
<React.Fragment>
<SongControls />
<TrackTime stepCount={state.getStepCount()} />
{isPlaying && <PlayingTrackList tracks={tracks} />}
{!isPlaying && (
<React.Fragment>
Expand Down
77 changes: 47 additions & 30 deletions src/components/song-composition/track.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { List } from "immutable";
import { Reactronica } from "lib/reactronica";
import { pick } from "lodash";
import { FileRecord } from "models/file-record";
import { InstrumentRecord } from "models/instrument-record";
import { TrackRecord } from "models/track-record";
import { TrackSectionRecord } from "models/track-section-record";
import { TrackSectionStepRecord } from "models/track-section-step-record";
import { useMemo } from "react";
import { intersectionWith } from "utils/collection-utils";
import { getFileById, toInstrumentMap, toSequencerMap } from "utils/file-utils";
import { useReactronicaState } from "utils/hooks/use-reactronica-state";
import { useTrackSectionsState } from "utils/hooks/use-track-sections-state";
Expand All @@ -22,28 +26,28 @@ interface TrackProps {
const Track: React.FC<TrackProps> = (props: TrackProps) => {
const { track, files, instruments } = props;
const { mute, solo, instrument_id } = track;
const { onStepPlay } = useReactronicaState();
const { state } = useWorkstationState();
const { onStepPlay, state: reactronicaState } = useReactronicaState();
const { startIndex, endIndex } = reactronicaState;
const { state: workstationState } = useWorkstationState();
const { state: trackSections } = useTrackSectionsState({
trackId: track.id,
});

const trackSectionSteps = useMemo(() => {
const trackSectionIds = trackSections.map(
(trackSection) => trackSection.id
);
return state.trackSectionSteps.filter((trackSectionStep) =>
trackSectionIds.includes(trackSectionStep.track_section_id)
);
}, [state.trackSectionSteps, trackSections]);
const trackSectionSteps = useMemo(
() =>
intersectionWith(
workstationState.trackSectionSteps,
trackSections,
(
trackSectionStep: TrackSectionStepRecord,
trackSection: TrackSectionRecord
) => trackSectionStep.track_section_id === trackSection.id
),
[trackSections, workstationState.trackSectionSteps]
);

const instrument = useMemo(
() =>
instrument_id != null
? instruments.find(
(instrument) => instrument.id === instrument_id
)
: undefined,
() => instruments.find((instrument) => instrument.id === instrument_id),
[instrument_id, instruments]
);
const instrumentFile = useMemo(
Expand All @@ -58,26 +62,39 @@ const Track: React.FC<TrackProps> = (props: TrackProps) => {
: toInstrumentMap(instrumentFile),
[files, instrumentFile, track]
);
const steps = useMemo(
() =>
track.isSequencer()
? toSequencerStepTypes(trackSections, trackSectionSteps, files)
: toInstrumentStepTypes(
trackSections,
trackSectionSteps,
instrument
),
[files, instrument, track, trackSectionSteps, trackSections]
);
const steps = useMemo(() => {
const steps = track.isSequencer()
? toSequencerStepTypes(trackSections, trackSectionSteps, files)
: toInstrumentStepTypes(
trackSections,
trackSectionSteps,
instrument
);

if (startIndex != null) {
return steps.slice(
startIndex,
endIndex != null ? endIndex + 1 : undefined
);
}

return steps;
}, [
endIndex,
files,
instrument,
startIndex,
track,
trackSectionSteps,
trackSections,
]);

const instrumentOptions = useMemo(() => {
if (instrument == null) {
return undefined;
}

const { curve, release } = instrument;

return { curve, release };
return pick(instrument, "curve", "release");
}, [instrument]);

return (
Expand Down
37 changes: 0 additions & 37 deletions src/components/time-grid.tsx

This file was deleted.

46 changes: 46 additions & 0 deletions src/components/tracks/track-time/track-time-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { majorScale, Pane, Text } from "evergreen-ui";
import { useTheme } from "utils/hooks/use-theme";
import { css, hover } from "glamor";
import { useReactronicaState } from "utils/hooks/use-reactronica-state";

interface TrackTimeCardProps {
index: number;
isPlaying: boolean;
stepCount: number;
}

const TrackTimeCard: React.FC<TrackTimeCardProps> = (
props: TrackTimeCardProps
) => {
const { index, isPlaying } = props;
const { colors } = useTheme();
const { onIndexClick, isSelected } = useReactronicaState();
const activeProps = isPlaying
? {
transform: "translateY(-2px)",
}
: {};

const className = css(hover({ transform: "translateY(-2px)" })).toString();

return (
<Pane
{...activeProps}
alignItems="center"
backgroundColor={isSelected(index) ? colors.blue200 : undefined}
className={className}
cursor="pointer"
display="flex"
flexDirection="row"
height={majorScale(2)}
justifyContent="center"
onClick={onIndexClick(index)}
width={majorScale(2)}>
<Text cursor="pointer" fontSize="x-small" userSelect="none">
{index + 1}
</Text>
</Pane>
);
};

export { TrackTimeCard };
33 changes: 33 additions & 0 deletions src/components/tracks/track-time/track-time.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { TrackTimeCard } from "components/tracks/track-time/track-time-card";
import { majorScale, Pane } from "evergreen-ui";
import { range } from "lodash";
import { useReactronicaState } from "utils/hooks/use-reactronica-state";

interface TrackTimeProps {
stepCount: number;
}

const TrackTime: React.FC<TrackTimeProps> = (props: TrackTimeProps) => {
const { stepCount } = props;
const { state } = useReactronicaState();
const { index: playingIndex, isPlaying } = state;
return (
<Pane
display="flex"
flexDirection="row"
marginLeft={majorScale(24)}
marginTop={-16}
position="absolute">
{range(0, stepCount).map((index: number) => (
<TrackTimeCard
index={index}
isPlaying={isPlaying && index === playingIndex}
key={index}
stepCount={stepCount}
/>
))}
</Pane>
);
};

export { TrackTime };
6 changes: 6 additions & 0 deletions src/interfaces/reactronica-state.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { StepNoteType } from "lib/reactronica";

interface ReactronicaState {
/** Step index to stop playing the track at */
endIndex?: number;
/** Current index being played */
index?: number;
isMuted: boolean;
isPlaying: boolean;
/** Current notes being played */
notes: StepNoteType[];
/** Step index to start playing the track at */
startIndex?: number;
}

export type { ReactronicaState };
29 changes: 11 additions & 18 deletions src/models/workstation-state-record.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DemoInstrument } from "enums/demo-instrument";
import { List, Record } from "immutable";
import { WorkstationState } from "interfaces/workstation-state";
import _ from "lodash";
import _, { sumBy } from "lodash";
import { BaseRecord } from "models/base-record";
import { FileRecord } from "models/file-record";
import { ProjectRecord } from "models/project-record";
Expand All @@ -20,7 +20,6 @@ import {
import { makeDefaultValues } from "utils/core-utils";
import { findKick, findHat, findOpenHat, findSnare } from "utils/file-utils";
import { MidiNoteUtils } from "utils/midi-note-utils";
import { getByTrackSection } from "utils/track-section-step-utils";
import { getByTrack } from "utils/track-section-utils";

interface WorkstationStateDiff {
Expand Down Expand Up @@ -231,23 +230,17 @@ class WorkstationStateRecord
return diff;
}

public getTrackSectionsByTrack(
track: TrackRecord
): List<TrackSectionRecord> {
return getByTrack(track, this.trackSections);
}
public getStepCount(): number {
// Calculate sum of steps by track
const stepSums = this.tracks.map((track) => {
const trackSections = getByTrack(track, this.trackSections);
return sumBy(
trackSections.toArray(),
(trackSection) => trackSection.step_count
);
});

public getTrackSectionStepsByTrack(
track: TrackRecord
): List<TrackSectionStepRecord> {
const trackSections = this.getTrackSectionsByTrack(track);
const trackSectionSteps = trackSections
.map((trackSection) =>
getByTrackSection(trackSection, this.trackSectionSteps)
)
.flatten()
.toList() as List<TrackSectionStepRecord>;
return trackSectionSteps;
return stepSums.max()!;
}

public isDemo(): boolean {
Expand Down
1 change: 1 addition & 0 deletions src/utils/atoms/reactronica-atom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const initialReactronicaState: ReactronicaState = {
isPlaying: false,
isMuted: false,
notes: [],
startIndex: undefined,
};
const ReactronicaStateAtom = atom<ReactronicaState>(initialReactronicaState);

Expand Down
Loading