Skip to content

Commit 349a407

Browse files
authored
feat: Record & Repeat buttons, region button rework (#3)
- Added toggle record button to - Added toggle repeat button to - Reworked region/song button - added region index, current time in seconds, and in beats over the track - Changed font in a label for db to monospace
1 parent 8e31603 commit 349a407

File tree

15 files changed

+223
-64
lines changed

15 files changed

+223
-64
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules
2-
dist
2+
dist
3+
media

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ The example project has some more tracks so here is a brief explanation of their
7070
- The input group is for tracks having a physical input source, like a guitar or a microphone. If any additional processing is needed (compression, eq, reverb), it should be put on these tracks
7171
- The output group is for tracks with physical outputs. All of these tracks have plugins for basic hearing safety - a `-10 dB` gain and a brick-wall limiter at `0 dB`. These tracks have "Receives" from input group tracks and audio group tracks.
7272

73-
7473
## Notes
7574

7675
When a specific song (region) is selected from the UI, it selects the region and sets the cursor to the beginning of the region. Selecting the region allows stopping the playback automatically when the region ends. For REAPER to stop the playback automatically you need to check `Preferences` -> `Audio` -> `Playback` -> `[x] Stop playback at end of loop if repeat is disabled.` and disable repeat in your project.

screenshots/Web - Control.png

10.4 KB
Loading

screenshots/Web - Mix.png

13.1 KB
Loading

src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { Icons } from "./Components/UI/Icons";
77
import { BottomNavigation } from "./Components/UI/BottomNavigation";
88

99
const SUBSCRIPTIONS = [
10-
{ request: "TRANSPORT", interval: 2000 },
10+
{ request: "TRANSPORT", interval: 100 },
1111
// Query every 100ms for peak levels
1212
{ request: "TRACK", interval: 100 },
1313
{ request: "REGION", interval: 4000 },

src/Components/Control/Playback.tsx

Lines changed: 49 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,36 +4,57 @@ import { Icons } from "../UI/Icons";
44

55
export function MainControl() {
66
const {
7-
data: { playState: state },
8-
actions: { play, pause, stop },
7+
data: { playState: state, recording, repeat },
8+
actions: { play, pause, stop, record, toggleRepeat },
99
} = useReaper();
10+
1011
return (
11-
<div class="flex justify-center rounded-md shadow-sm" role="group">
12-
<button
13-
type="button"
14-
onClick={play}
15-
class={`btn-primary rounded-l w-16 ${
16-
state() == PlayState.Playing && "selected"
17-
}`}
18-
>
19-
{Icons.Play}
20-
</button>
21-
<button
22-
type="button"
23-
onClick={pause}
24-
class={`btn-primary w-16 ${state() == PlayState.Paused && "selected"}`}
25-
>
26-
{Icons.Pause}
27-
</button>
28-
<button
29-
type="button"
30-
onClick={stop}
31-
class={`btn-primary w-16 rounded-r ${
32-
state() == PlayState.Stopped && "selected"
33-
}`}
34-
>
35-
{Icons.Stop}
36-
</button>
12+
<div class="flex justify-center space-x-4">
13+
<div class="flex justify-center rounded-md shadow-sm" role="group">
14+
<button
15+
type="button"
16+
onClick={play}
17+
class={`btn-primary rounded-l w-14 ${
18+
state() == PlayState.Playing && "selected"
19+
}`}
20+
>
21+
{Icons.Play}
22+
</button>
23+
<button
24+
type="button"
25+
onClick={pause}
26+
class={`btn-primary w-14 ${state() == PlayState.Paused && "selected"}`}
27+
>
28+
{Icons.Pause}
29+
</button>
30+
<button
31+
type="button"
32+
onClick={stop}
33+
class={`btn-primary w-14 rounded-r ${
34+
state() == PlayState.Stopped && "selected"
35+
}`}
36+
>
37+
{Icons.Stop}
38+
</button>
39+
</div>
40+
41+
<div class="flex justify-center rounded-md shadow-sm" role="group">
42+
<button
43+
type="button"
44+
onClick={record}
45+
class={`btn-primary btn-primary-red w-14 rounded-l ${recording() && "selected"}`}
46+
>
47+
{Icons.Record}
48+
</button>
49+
50+
<button
51+
type="button"
52+
onClick={toggleRepeat}
53+
class={`btn-primary w-14 rounded-r ${repeat() && "selected"}`}
54+
>
55+
{Icons.Repeat}
56+
</button>
57+
</div>
3758
</div>
3859
);
3960
}

src/Components/Control/Regions.tsx

Lines changed: 45 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,42 @@
11
import { createSelector, For } from "solid-js";
22
import { useReaper } from "../../Data/Context";
3-
import { Region } from "../../Data/State";
3+
import { CurrentTime, Region } from "../../Data/State";
44

5-
function Progress(p: { progress: number }) {
5+
function progress(region: Region, currentTime: number): number {
6+
return Math.max(
7+
0,
8+
Math.min(
9+
100,
10+
((currentTime - region.startTime) / (region.endTime - region.startTime)) *
11+
100,
12+
),
13+
);
14+
}
15+
16+
function time(totalSeconds: number): string {
17+
if (totalSeconds < 1) {
18+
return "0:00";
19+
}
20+
const minutes = Math.floor(totalSeconds / 60);
21+
const seconds = Math.floor(totalSeconds % 60);
22+
23+
return `${minutes}:${seconds.toString().padStart(2, "0")}`;
24+
}
25+
26+
function Progress(p: { region: Region; currentTime: CurrentTime }) {
627
return (
7-
<div
8-
class="absolute bottom-0 left-0 top-0 bg-gray-400 opacity-25"
9-
style={`width: ${p.progress}%`}
10-
/>
28+
<>
29+
<div
30+
class="absolute bottom-0 left-0 top-0 right-0 bg-gray-400 opacity-25"
31+
style={`width: ${progress(p.region, p.currentTime.seconds)}%`}
32+
/>
33+
34+
<div class="absolute top-0 bottom-0 right-0 flex items-center opacity-70 px-3">
35+
<p class="text-xs text-gray-400 bg-neutral-900/40 rounded-lg p-1 font-mono">
36+
{time(p.currentTime.seconds)} / {p.currentTime.beats}
37+
</p>
38+
</div>
39+
</>
1140
);
1241
}
1342

@@ -19,18 +48,10 @@ export function Regions() {
1948

2049
const isPlaying = createSelector(
2150
currentTime,
22-
(r: Region, t) => t >= Math.floor(r.startTime) && t <= Math.ceil(r.endTime),
51+
(r: Region, { seconds: t }) =>
52+
t >= Math.floor(r.startTime) && t <= Math.ceil(r.endTime),
2353
);
2454

25-
const progress = (r: Region) =>
26-
Math.max(
27-
0,
28-
Math.min(
29-
100,
30-
((currentTime() - r.startTime) / (r.endTime - r.startTime)) * 100,
31-
),
32-
);
33-
3455
return (
3556
<div>
3657
<h2 class="mb-2 text-m font-extrabold leading-none tracking-tight text-gray-900 dark:text-white">
@@ -41,13 +62,18 @@ export function Regions() {
4162
{(region) => (
4263
<button
4364
type="button"
44-
class={`relative overflow-clip btn-outlined my-1 px-5 ${
65+
class={`relative grow flex h-10 overflow-clip btn-outlined my-1 px-3 ${
4566
isPlaying(region) && "selected"
4667
}`}
4768
onClick={() => moveToRegion(region)}
4869
>
49-
{isPlaying(region) && <Progress progress={progress(region)} />}
50-
<span class="relative">{region.name}</span>
70+
{isPlaying(region) && (
71+
<Progress region={region} currentTime={currentTime()} />
72+
)}
73+
74+
<span class="relative">
75+
{region.id}. {region.name}
76+
</span>
5177
</button>
5278
)}
5379
</For>

src/Components/Mix/SendControl.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ function Slider(p: SliderProps) {
6969
<p
7070
class={`text-xs text-white ${
7171
input() != null ? "text-gray-400" : ""
72-
} bg-neutral-900/40 rounded-lg p-1`}
72+
} bg-neutral-900/40 rounded-lg p-1 font-mono`}
7373
>
7474
{input() == null && normalizedToDb(p.value).toFixed(2)}
7575
{input() != null && normalizedToDb(input() ?? 0).toFixed(2)} dB

src/Components/UI/Icons.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,38 @@ const Stop = (
4949
</svg>
5050
);
5151

52+
const Repeat = (
53+
<svg
54+
xmlns="http://www.w3.org/2000/svg"
55+
fill="none"
56+
viewBox="0 0 24 24"
57+
stroke-width="1.5"
58+
stroke="currentColor"
59+
>
60+
<path
61+
stroke-linecap="round"
62+
stroke-linejoin="round"
63+
d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99"
64+
/>
65+
</svg>
66+
);
67+
68+
const Record = (
69+
<svg
70+
xmlns="http://www.w3.org/2000/svg"
71+
fill="none"
72+
viewBox="0 0 24 24"
73+
stroke-width={1.5}
74+
stroke="currentColor"
75+
>
76+
<path
77+
stroke-linecap="round"
78+
stroke-linejoin="round"
79+
d="M12 18.75a6 6 0 0 0 6-6v-1.5m-6 7.5a6 6 0 0 1-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 0 1-3-3V4.5a3 3 0 1 1 6 0v8.25a3 3 0 0 1-3 3Z"
80+
/>
81+
</svg>
82+
);
83+
5284
const Control = (p: { class?: string }) => (
5385
<svg
5486
class={`w-6 h-6 mb-1 ${p.class}`}
@@ -89,6 +121,8 @@ export const Icons = {
89121
Play,
90122
Pause,
91123
Stop,
124+
Repeat,
125+
Record,
92126

93127
Control,
94128
Mix,

src/Data/Actions.tsx

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ interface PauseAction {
77
interface StopAction {
88
type: "Stop";
99
}
10+
interface RecordAction {
11+
type: "Record";
12+
}
1013

1114
interface MoveAction {
1215
type: "Move";
@@ -33,18 +36,25 @@ interface ToggleSendMuteAction {
3336
send: string;
3437
}
3538

39+
interface ToggleRepeatAction {
40+
type: "ToggleRepeat";
41+
}
42+
3643
export type Action =
3744
| PlayAction
3845
| PauseAction
3946
| StopAction
47+
| RecordAction
4048
| MoveAction
4149
| SetTrackVolumeAction
4250
| SetSendVolumeAction
43-
| ToggleSendMuteAction;
51+
| ToggleSendMuteAction
52+
| ToggleRepeatAction;
4453

4554
export function reduceActions(actions: Action[]): Action[] {
46-
let control: (PlayAction | PauseAction | StopAction)[] = [];
55+
let control: (PlayAction | PauseAction | StopAction | RecordAction)[] = [];
4756
let move: MoveAction[] = [];
57+
let repeat: ToggleRepeatAction[] = [];
4858
let trackVolume: { [k in number]: SetTrackVolumeAction } = {};
4959
let sendVolume: { [k in number]: { [k in string]: SetSendVolumeAction } } =
5060
{};
@@ -55,11 +65,15 @@ export function reduceActions(actions: Action[]): Action[] {
5565
case "Play":
5666
case "Pause":
5767
case "Stop":
68+
case "Record":
5869
control = [action];
5970
break;
6071
case "Move":
6172
move = [action];
6273
break;
74+
case "ToggleRepeat":
75+
repeat = [action];
76+
break;
6377
case "SetTrackVolume":
6478
trackVolume[action.track] = action;
6579
break;
@@ -80,6 +94,7 @@ export function reduceActions(actions: Action[]): Action[] {
8094
return [
8195
...control,
8296
...move,
97+
...repeat,
8398
...Object.values(trackVolume),
8499
...Object.values(sendVolume).flatMap((s) => Object.values(s)),
85100
...Object.entries(sendMutes).flatMap(([track, sends]) =>
@@ -107,6 +122,10 @@ export function actionsToCommands(actions: Action[]): string {
107122
return "1008;TRANSPORT";
108123
case "Stop":
109124
return "40667;TRANSPORT";
125+
case "Record":
126+
return "1013;TRANSPORT";
127+
case "ToggleRepeat":
128+
return "1068;TRANSPORT";
110129
case "Move":
111130
return `SET/POS/${action.end};40626;SET/POS/${action.pos};40625;TRANSPORT`;
112131
case "SetTrackVolume":

0 commit comments

Comments
 (0)