Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions mamar-web/src/app/InstrumentInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ export default function InstrumentInput({ index, onChange }: Props) {
<NumberField label="Reverb" value={instrument.reverb} onChange={reverb => dispatch({ type: "update_instrument", index, partial: { reverb } })} />
</Flex>
<Flex gap="size-150">
<NumberField label="Coarse tune" value={instrument.coarseTune} onChange={coarseTune => dispatch({ type: "update_instrument", index, partial: { coarseTune } })} />
<NumberField label="Fine tune" value={instrument.fineTune} onChange={fineTune => dispatch({ type: "update_instrument", index, partial: { fineTune } })} />
<NumberField label="Coarse tune" value={instrument.coarse_tune} onChange={coarse_tune => dispatch({ type: "update_instrument", index, partial: { coarse_tune } })} />
<NumberField label="Fine tune" value={instrument.fine_tune} onChange={fine_tune => dispatch({ type: "update_instrument", index, partial: { fine_tune } })} />
</Flex>
</Form>}
</Content>
Expand Down
76 changes: 56 additions & 20 deletions mamar-web/src/app/doc/Ruler.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Button, ButtonGroup, Content, Dialog, DialogTrigger, Divider, Form, Heading, NumberField, Switch } from "@adobe/react-spectrum"
import classNames from "classnames"
import { Segment } from "pm64-typegen"
import { Event, Segment } from "pm64-typegen"
import { useState } from "react"
import { usePress } from "react-aria"

Expand All @@ -9,6 +9,7 @@ import styles from "./Ruler.module.scss"
import { useTime } from "./timectx"

import { useBgm, useSegment, useVariation } from "../store"
import { getSegmentId } from "../store/segment"

interface Loop {
id: number
Expand All @@ -18,20 +19,25 @@ interface Loop {
}

function getLoops(segments: Segment[]): Loop[] {
const loops = []
const loops: Loop[] = []

for (let startIdx = 0; startIdx < segments.length; startIdx++) {
const start = segments[startIdx]
if (start.type === "StartLoop") {
if ("StartLoop" in start) {
// Look for EndLoop
for (let endIdx = 0; endIdx < segments.length; endIdx++) {
const end = segments[endIdx]
if (end.type === "EndLoop" && end.label_index === start.label_index) {
if ("EndLoop" in end && end.EndLoop.label_index === start.StartLoop.label_index) {
if (start.StartLoop.id == null) {
console.error("Segment", start, "does not have an ID")
continue
}

loops.push({
id: start.id,
id: start.StartLoop.id,
start: startIdx,
end: endIdx,
iterCount: end.iter_count,
iterCount: end.EndLoop.iter_count,
})
break
}
Expand Down Expand Up @@ -108,11 +114,13 @@ export function useSegmentLengths(): number[] {
const segments = variation?.segments ?? []

return segments.map(segment => {
if (bgm && segment.type === "Subseg") {
const master = bgm.trackLists[segment.trackList].tracks[0]
return master.commands.reduce((totalDelay, event) => {
if (event.type === "Delay") {
return totalDelay + event.value
if (bgm && "Subseg" in segment) {
const master = bgm.track_lists[segment.Subseg.track_list].tracks[0]
const commands = master.commands as unknown as Event[]

return commands.reduce((totalDelay, event) => {
if ("Delay" in event) {
return totalDelay + event.Delay
} else {
return totalDelay
}
Expand All @@ -139,18 +147,24 @@ export default function Ruler() {
let totalTime = 0
for (let i = 0; i < segmentLengths.length; i++) {
const segment = segments[i]
const id = getSegmentId(segment)
if (id == null) {
console.error("Segment", segment, "does not have an ID")
continue
}

let length = segmentLengths[i]

if (length === 0) {
// Loop or other, so check for loop handle
for (const loop of loops) {
if (i === loop.start) {
currentLoop = loop
elements.push(<LoopHandle key={`start_loop_${loop.id}`} segment={segment.id} kind="start" loop={loop} setHighlightedLoop={setHighlightedLoop} />)
elements.push(<LoopHandle key={`start_loop_${loop.id}`} segment={id} kind="start" loop={loop} setHighlightedLoop={setHighlightedLoop} />)
}
if (i === loop.end) {
currentLoop = null
elements.push(<LoopHandle key={`end_loop_${loop.id}`} segment={segment.id} kind="end" loop={loop} setHighlightedLoop={setHighlightedLoop} />)
elements.push(<LoopHandle key={`end_loop_${loop.id}`} segment={id} kind="end" loop={loop} setHighlightedLoop={setHighlightedLoop} />)
}
continue
}
Expand All @@ -159,18 +173,18 @@ export default function Ruler() {

if (currentLoop !== null) {
// Consume all segments that are part of this loop
while (segments[i + 1].type !== "EndLoop") {
while (!("EndLoop" in segments[i + 1])) {
length += segmentLengths[++i]
}

const loop = Object.assign({}, currentLoop!) // Avoids stale currentLoop reference in dialog func below

elements.push(<DialogTrigger key={segment.id} >
elements.push(<DialogTrigger key={id} >
<RulerSegment segment={segment} currentLoop={currentLoop} highlightedLoop={highlightedLoop} length={length} />
{close => <LoopDialog loop={loop} close={close} />}
</DialogTrigger>)
} else {
elements.push(<RulerSegment key={segment.id} segment={segment} currentLoop={currentLoop} highlightedLoop={highlightedLoop} length={length} />)
elements.push(<RulerSegment key={id} segment={segment} currentLoop={currentLoop} highlightedLoop={highlightedLoop} length={length} />)
}

totalTime += length
Expand Down Expand Up @@ -214,9 +228,15 @@ function RulerSegment({ segment, currentLoop, highlightedLoop, length, onPress }
style={ticksToStyle(length)}
title={currentLoop === null ? "Double-click to loop" : ""}
onDoubleClick={() => {
const id = getSegmentId(segment)
if (id == null) {
console.error("Segment", segment, "does not have an ID")
return
}

dispatch({
type: "toggle_segment_loop",
id: segment.id,
id,
})
}}
>
Expand All @@ -228,7 +248,12 @@ function RulerSegment({ segment, currentLoop, highlightedLoop, length, onPress }

function LoopDialog({ loop, close }: { loop: Loop, close: () => void }) {
const [variation, variationDispatch] = useVariation()
const [, endDispatch] = useSegment(variation?.segments[loop.end].id)

const end = variation?.segments[loop.end]
const id = (end && "EndLoop" in end) ? end.EndLoop.id : undefined
console.assert(end && "EndLoop" in end, "Segment", end, "is not EndLoop")

const [, endDispatch] = useSegment(id)

function setIterCount(iterCount: number) {
endDispatch({
Expand All @@ -239,11 +264,22 @@ function LoopDialog({ loop, close }: { loop: Loop, close: () => void }) {

function deleteLoop() {
const start = variation?.segments[loop.start]
if (start)
if (start) {
if (!("StartLoop" in start)) {
console.error("Segment", start, "is not StartLoop")
return
}

if (start.StartLoop.id == null) {
console.error("Segment", start, "does not have an ID")
return
}

variationDispatch({
type: "toggle_segment_loop",
id: start.id,
id: start.StartLoop.id,
})
}
}

return <Dialog size="S">
Expand Down
36 changes: 20 additions & 16 deletions mamar-web/src/app/doc/SegmentMap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import TrackControls from "../emu/TrackControls"
import { useBgm, useDoc, useVariation } from "../store"
import useSelection, { SelectionProvider } from "../util/hooks/useSelection"
import { Track } from "pm64-typegen"
import { getSegmentId } from "../store/segment"

const TRACK_HEAD_WIDTH = 100 // Match with $trackHead-width

Expand All @@ -21,7 +22,7 @@ function hasParentTrack({ polyphony }: Track): boolean {
function PianoRollThumbnail({ trackIndex, trackListIndex }: { trackIndex: number, trackListIndex: number }) {
const [doc, dispatch] = useDoc()
const [bgm] = useBgm()
const track = bgm?.trackLists[trackListIndex]?.tracks[trackIndex]
const track = bgm?.track_lists[trackListIndex]?.tracks[trackIndex]
const isSelected = doc?.panelContent.type === "tracker" && doc?.panelContent.trackList === trackListIndex && doc?.panelContent.track === trackIndex
const nameId = useId()
const commands = useDeferredValue(track?.commands)
Expand Down Expand Up @@ -49,8 +50,8 @@ function PianoRollThumbnail({ trackIndex, trackListIndex }: { trackIndex: number
aria-labelledby={nameId}
className={classNames({
[styles.pianoRollThumbnail]: true,
[styles.drumRegion]: track.isDrumTrack,
[styles.disabledRegion]: track.isDisabled,
[styles.drumRegion]: track.is_drum_track,
[styles.disabledRegion]: track.is_disabled,
[styles.hasInterestingParentTrack]: hasParentTrack(track),
[styles.selected]: isSelected,
})}
Expand Down Expand Up @@ -83,11 +84,11 @@ const Thumbnail = memo(({ commands }: { commands: Event[] }) => {
let maxPitch = 0
let hasNoteInRange = false
for (const command of commands) {
if (command.type === "Note") {
minPitch = Math.min(minPitch, command.pitch)
maxPitch = Math.max(maxPitch, command.pitch)
if ("Note" in command) {
minPitch = Math.min(minPitch, command.Note.pitch)
maxPitch = Math.max(maxPitch, command.Note.pitch)

if (command.pitch >= c2 && command.pitch <= c5) {
if (command.Note.pitch >= c2 && command.Note.pitch <= c5) {
hasNoteInRange = true
}
}
Expand All @@ -108,16 +109,16 @@ const Thumbnail = memo(({ commands }: { commands: Event[] }) => {
const notes = []
let time = 0
for (const command of commands) {
if (command.type === "Note") {
if ("Note" in command) {
notes.push(<rect
key={command.id}
x={time}
y={command.pitch - minPitch}
width={command.length}
y={command.Note.pitch - minPitch}
width={command.Note.length}
height={1}
/>)
} else if (command.type === "Delay") {
time += command.value
} else if ("Delay" in command) {
time += command.Delay
}
}

Expand Down Expand Up @@ -177,16 +178,19 @@ function Container() {
{i > 0 && <TrackControls trackIndex={i} />}
</div>}
{variation.segments.map((segment, segmentIndex) => {
if (segment.type === "Subseg") {
if ("Subseg" in segment) {
return <View
key={segment.id}
key={segment.Subseg.id}
colorVersion={6}
UNSAFE_style={ticksToStyle(segmentLengths[segmentIndex])}
>
<PianoRollThumbnail trackIndex={i} trackListIndex={segment.trackList} />
<PianoRollThumbnail trackIndex={i} trackListIndex={segment.Subseg.track_list} />
</View>
} else {
return <div key={segment.id} />
const id = getSegmentId(segment)
console.assert(id != null, "Segment", segment, "does not have an ID")

return <div key={id} />
}
})}
</div>)}
Expand Down
11 changes: 6 additions & 5 deletions mamar-web/src/app/doc/SubsegDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import styles from "./SubsegDetails.module.scss"
import Tracker from "./Tracker"

import { useBgm } from "../store"
import { Polyphony } from "pm64-typegen"
import { Bgm, Polyphony } from "pm64-typegen"
import { BgmAction } from "../store/bgm"

export interface Props {
trackListId: number
Expand All @@ -15,8 +16,8 @@ export interface Props {

export default function SubsegDetails({ trackListId, trackIndex }: Props) {
const hid = useId()
const [bgm, dispatch] = useBgm()
const track = bgm?.trackLists[trackListId]?.tracks[trackIndex]
const [bgm, dispatch]: [Bgm | undefined, (action: BgmAction) => void] = useBgm()
const track = bgm?.track_lists[trackListId]?.tracks[trackIndex]

// Track name editing is debounced to prevent dispatch spam when typing
const [name, setName] = useState(track?.name)
Expand All @@ -42,9 +43,9 @@ export default function SubsegDetails({ trackListId, trackIndex }: Props) {
value={name}
onChange={setName}
/>
<Switch isSelected={!track.isDisabled} onChange={v => dispatch({ type: "modify_track_settings", trackList: trackListId, track: trackIndex, isDisabled: !v })}>Enabled</Switch>
<Switch isSelected={!track.is_disabled} onChange={v => dispatch({ type: "modify_track_settings", trackList: trackListId, track: trackIndex, isDisabled: !v })}>Enabled</Switch>
{trackIndex !== 0 ? <>
<Switch isSelected={track.isDrumTrack} onChange={isDrumTrack => dispatch({ type: "modify_track_settings", trackList: trackListId, track: trackIndex, isDrumTrack })}>Percussion</Switch>
<Switch isSelected={track.is_drum_track} onChange={isDrumTrack => dispatch({ type: "modify_track_settings", trackList: trackListId, track: trackIndex, isDrumTrack })}>Percussion</Switch>
<PolyphonyForm {...track} maxParentTrackIdx={trackIndex - 1} onChange={polyphony => {
dispatch({ type: "modify_track_settings", trackList: trackListId, track: trackIndex, polyphony })
}} />
Expand Down
Loading
Loading