diff --git a/README.md b/README.md
index 09e3eff..81b9eed 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,20 @@
# Obsidian Metronome 🎵
-Use this plugin to add interactive metronomes to your notes. Use just the visual metronome or turn on a click or beep. Customize the tempo, meter, sound, size, and more. Pair it with [obsidian-plugin-abcjs](https://github.com/TilBlechschmidt/obsidian-plugin-abcjs) to have your score and metronome living in harmony in a single note! 🎵
+Use this plugin to add interactive metronomes to your notes. Use just the visual metronome or turn on a click or beep. Customize the tempo, meter, instrument, size, and more. Pair it with [obsidian-plugin-abcjs](https://github.com/TilBlechschmidt/obsidian-plugin-abcjs) to have your score and metronome living in harmony in a single note!
+
+This markdown in your Obsidian note:
+
+````markdown
+# My Metronome
+
+```metronome
+bpm: 120
+meter: 3/4
+size: medium
+```
+````
+
+Produces this interactive metronome:
![Kiku](images/demo-1.gif)
@@ -8,13 +22,16 @@ Use this plugin to add interactive metronomes to your notes. Use just the visual
I use [obsidian-plugin-abcjs](https://github.com/TilBlechschmidt/obsidian-plugin-abcjs) for writing out music notation in Obsidian and sometimes during live performances I have a need to know what tempo to play a song. This plugin provides a visual metronome that I can have right alongside my music that serves as a great reminder of the correct tempo for a live performance. During practice, I can unmute it and practice alongside it as it clicks on the beat.
-## Getting Started
+## Install
1. In Obsidian, go to Settings > Community Plugins and disable safe mode if it is enabled.
1. Select **Browse** and search for the **Metronome** plugin.
1. Install the plugin.
1. Enable the plugin.
-1. Open a note and place a code block in your note like the one below. Give it a `bpm` ([beats per minute](https://en.wikipedia.org/wiki/Tempo)) for your tempo. In the example below, I've set `bpm` to `120`.
+
+## Usage
+
+Open a note and place a code block in your note like the one below. Give it a `bpm` ([beats per minute](https://en.wikipedia.org/wiki/Tempo)) for your tempo. In the example below, I've set `bpm` to `120`.
````markdown
# My basic metronome
@@ -37,9 +54,9 @@ You can customize the metronome further:
bpm: 120
meter: 12/8
size: large
-sound: beep
-beepTick: A5
-beepTock: A4
+instrument: beep
+tickNotes: A5
+tockNotes: A4
```
````
@@ -49,23 +66,23 @@ beepTock: A4
These are all the options you can change when creating a metronome. Only the `bpm` option is required.
-| Option | Type | Description | Default |
-| ----------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
-| `bpm` | number | Tempo in [beats per minute](https://en.wikipedia.org/wiki/Tempo). Must be greater than 0. | (required) |
-| `meter` | string | The [time signature](https://en.wikipedia.org/wiki/Time_signature) (meter) to play in. The bottom number must be 1, 2, 4, 8, 16, 32, or 64. Examples: 4/4, 3/4, 6/8, 12/8. | `4/4` |
-| `muted` | `yes` or `no` | Whether or not the metronome's audio starts muted. | `yes` |
-| `autoStart` | `yes` or `no` | Whether or not the metronome starts flashing visually right away. If `autoStart` is `yes` and `muted` is `no`, then the metronome's sound will also start playing immediately. | `yes` |
-| `size` | `small`, `medium`, `large`, or `xlarge` | Control the size of the metronome in the note. | `small` |
-| `style` | `pulse`, `pendulum`, `line` | Control the style of the metronome. `pulse` makes the whole area flash color. `pendulum` shows an illustration of a metronome with a swinging pendulum (works best with `large` and up sizes). `line` shows a vertical line moving left and right (works best with `large` and up sizes). | `pulse` |
-| `sound` | `click` or `beep` | Control whether the metronome clicks or beeps when unmuted. | `click` |
-| `beepTick` | string | When `sound` is set to `beep`, this determines the pitch of the **downbeat**, specified in [scientific pitch notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation). (For example, middle C is C4.) Multiple tones can be played at the same time by providing a comma-separated list of tones. If there is no meter, every beat is considered a downbeat. | `C6`
`F5,A5,C5` |
-| `beepTock` | string | When `sound` is set to `beep`, this determines the pitch of the **upbeat**, specified in [scientific pitch notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation). (For example, middle C is C4.) Multiple tones can be played at the same time by providing a comma-separated list of tones. If there is no meter, every beat is considered a downbeat. | `C5`
`F4,A4,C4 ` |
+| Option | Type | Description | Default |
+| ------------ | -------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
+| `bpm` | number | Tempo in [beats per minute](https://en.wikipedia.org/wiki/Tempo). Must be greater than 0. | (required) |
+| `meter` | string | The [time signature](https://en.wikipedia.org/wiki/Time_signature) (meter) to play in. The bottom number must be 1, 2, 4, 8, 16, 32, or 64. Examples: 4/4, 3/4, 6/8, 12/8. | `4/4` |
+| `muted` | `yes` or `no` | Whether or not the metronome's audio starts muted. | `yes` |
+| `autoStart` | `yes` or `no` | Whether or not the metronome starts flashing visually right away. If `autoStart` is `yes` and `muted` is `no`, then the metronome's sound will also start playing immediately. | `yes` |
+| `size` | `small`, `medium`, `large`, or `xlarge` | Control the size of the metronome in the note. | `small` |
+| `style` | `pulse`, `pendulum`, `line` | Control the style of the metronome. `pulse` makes the whole area flash color. `pendulum` shows an illustration of a metronome with a swinging pendulum (works best with `large` and up sizes). `line` shows a vertical line moving left and right (works best with `large` and up sizes). | `pulse` |
+| `instrument` | `click`, `beep`, `AMSynth`, `DuoSynth`, `FMSynth`, `MembraneSynth`, `MetalSynth`, `MonoSynth`, or `PluckSynth` | Change the metronome's instrument. | `click` |
+| `tickNotes` | string | This determines the note(s) played on the **downbeat**, specified in [scientific pitch notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation). (For example, middle C is C4.) Multiple tones can be played at the same time by providing a comma-separated list of tones. If there is no meter, every beat is considered a downbeat. Has no effect when `instrument` is `click`. | `C6`
`F5,A5,C5` |
+| `tockNotes` | string | This determines the note(s) played on the **upbeat**, specified in [scientific pitch notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation). (For example, middle C is C4.) Multiple tones can be played at the same time by providing a comma-separated list of tones. If there is no meter, every beat is considered a downbeat. Has no effect when `instrument` is `click`. | `C5`
`F4,A4,C4 ` |
## Examples
-### Small metronome in 6/8 time that does not autostart
+### Setting `autoStart: no` on a metronome
-Looks great in dark mode too!
+If a metronome has `autoStart: no`, it won't start flashing when you open the note. You can also edit metronomes inline using Obsidian's live preview feature.
![](images/demo-4.gif)
@@ -77,15 +94,88 @@ The metronome works great when placed alongside music notation using the [obsidi
### Play multiple tones at once
-Set `beepTick` and `beepTock` to a comma-separated list of tones to play those tones all at once in a chord.
+Set `tickNotes` and `tockNotes` to a comma-separated list of tones to play those tones all at once in a chord.
````markdown
```metronome
bpm: 80
meter: 4/4
-sound:beep
-beepTick: F4,A4,C4
-beepTock: D4,F4,A4
+instrument:beep
+tickNotes: F4,A4,C4
+tockNotes: D4,F4,A4
+```
+````
+
+### Using different instruments and tones
+
+````markdown
+```metronome
+bpm: 90
+meter: 4/4
+instrument: AMSynth
+tickNotes: C5,E5
+tockNotes: D5,F5
+```
+````
+
+````markdown
+```metronome
+bpm: 90
+meter: 4/4
+instrument: DuoSynth
+muted: no
+tickNotes: A3
+tockNotes: B3
+```
+````
+
+````markdown
+```metronome
+bpm: 90
+meter: 4/4
+instrument: FMSynth
+tickNotes: A4
+tockNotes: C#5,E5
+```
+````
+
+````markdown
+```metronome
+bpm: 90
+meter: 4/4
+instrument: MembraneSynth
+tickNotes: B6
+tockNotes: B5
+```
+````
+
+````markdown
+```metronome
+bpm: 90
+meter: 4/4
+instrument: MetalSynth
+tickNotes: G3
+tockNotes: D0
+```
+````
+
+````markdown
+```metronome
+bpm: 90
+meter: 4/4
+instrument: MonoSynth
+tickNotes: C5,E5,G5
+tockNotes: F5,A6,C6
+```
+````
+
+````markdown
+```metronome
+bpm: 90
+meter: 4/4
+instrument: PluckSynth
+tickNotes: A4
+tockNotes: E4
```
````
@@ -93,8 +183,7 @@ beepTock: D4,F4,A4
````markdown
```metronome
-bpm: 68
-meter: 3/4
+bpm: 125
size: large
style: pulse
```
@@ -108,8 +197,7 @@ Works best on `large` and `xlarge` sizes.
````markdown
```metronome
-bpm: 68
-meter: 3/4
+bpm: 125
size: large
style: pendulum
```
@@ -119,15 +207,30 @@ style: pendulum
### `line` style
-![](images/demo-8.gif)
-
Works best on `large` and `xlarge` sizes.
````markdown
```metronome
-bpm: 68
-meter: 3/4
+bpm: 125
size: large
style: line
```
````
+
+![](images/demo-8.gif)
+
+### Works with themes
+
+The metronome works great with Obsidian's themes in light or dark mode.
+
+Theme: Obsidian Default (Dark)
+
+![](images/demo-theme-obsidian-dark.gif)
+
+Theme: Minimal (Light)
+
+![](images/demo-theme-minimal-light.gif)
+
+Theme: [Obsidian Atom](https://github.com/kognise/obsidian-atom) (Dark)
+
+![](images/demo-theme-atom-dark.gif)
diff --git a/images/demo-1.gif b/images/demo-1.gif
index 30d1397..4ffb2c1 100644
Binary files a/images/demo-1.gif and b/images/demo-1.gif differ
diff --git a/images/demo-2.gif b/images/demo-2.gif
index b28350a..2a1de9a 100644
Binary files a/images/demo-2.gif and b/images/demo-2.gif differ
diff --git a/images/demo-3.gif b/images/demo-3.gif
index 8df2417..ca23903 100644
Binary files a/images/demo-3.gif and b/images/demo-3.gif differ
diff --git a/images/demo-4.gif b/images/demo-4.gif
index 886b6af..dd3ffbc 100644
Binary files a/images/demo-4.gif and b/images/demo-4.gif differ
diff --git a/images/demo-5.gif b/images/demo-5.gif
index 840d616..4122942 100644
Binary files a/images/demo-5.gif and b/images/demo-5.gif differ
diff --git a/images/demo-6.gif b/images/demo-6.gif
index c101594..4e01ad6 100644
Binary files a/images/demo-6.gif and b/images/demo-6.gif differ
diff --git a/images/demo-7.gif b/images/demo-7.gif
index 9ea4322..1d2e28e 100644
Binary files a/images/demo-7.gif and b/images/demo-7.gif differ
diff --git a/images/demo-8.gif b/images/demo-8.gif
index d3095ed..ca5f561 100644
Binary files a/images/demo-8.gif and b/images/demo-8.gif differ
diff --git a/images/demo-theme-atom-dark.gif b/images/demo-theme-atom-dark.gif
new file mode 100644
index 0000000..fab820f
Binary files /dev/null and b/images/demo-theme-atom-dark.gif differ
diff --git a/images/demo-theme-minimal-light.gif b/images/demo-theme-minimal-light.gif
new file mode 100644
index 0000000..3fdf107
Binary files /dev/null and b/images/demo-theme-minimal-light.gif differ
diff --git a/images/demo-theme-obsidian-dark.gif b/images/demo-theme-obsidian-dark.gif
new file mode 100644
index 0000000..7befc89
Binary files /dev/null and b/images/demo-theme-obsidian-dark.gif differ
diff --git a/src/components/Metronome.vue b/src/components/Metronome.vue
index c1f2890..055b719 100644
--- a/src/components/Metronome.vue
+++ b/src/components/Metronome.vue
@@ -6,12 +6,7 @@ import MetronomeToggle from "./MetronomeToggle.vue";
import { ref, watch, toRefs, onBeforeUnmount } from "vue";
import MetronomeIcon from "../components/MetronomeIcon.vue";
import StyleLine from "../components/StyleLine.vue";
-import {
- tick as playTick,
- tickUpbeat as playTickUpbeat,
- tock as playTock,
- beep as playBeep,
-} from "../sounds";
+import { playTick, playTickUpbeat, playTock, playSynth } from "../sounds";
import { useTick } from "../hooks/useTick";
import { useParentMarkdownWrapperVisibilityWatcher } from "../hooks/useParentMarkdownWrapperVisibilityWatcher";
import { useCSSAnimationSynchronizer } from "../hooks/useCSSAnimationSynchronizer";
@@ -23,9 +18,9 @@ const props = defineProps<{
size: MetronomeCodeBlockParameters["size"];
style: MetronomeCodeBlockParameters["style"];
autoStart: MetronomeCodeBlockParameters["autoStart"];
- sound: MetronomeCodeBlockParameters["sound"];
- beepTick: MetronomeCodeBlockParameters["beepTick"];
- beepTock: MetronomeCodeBlockParameters["beepTock"];
+ instrument: MetronomeCodeBlockParameters["instrument"];
+ tickNotes: MetronomeCodeBlockParameters["tickNotes"];
+ tockNotes: MetronomeCodeBlockParameters["tockNotes"];
}>();
const metronome = ref(null);
@@ -47,17 +42,23 @@ const haltAnimationStyle = useCSSAnimationSynchronizer({
onTick(
() =>
!muted.value &&
- (props.sound === "beep" ? playBeep(props.beepTick) : playTick())
+ (props.instrument === "click"
+ ? playTick()
+ : playSynth(props.tickNotes, props.instrument))
);
onTickAlternate(
() =>
!muted.value &&
- (props.sound === "beep" ? playBeep(props.beepTick) : playTickUpbeat())
+ (props.instrument === "click"
+ ? playTickUpbeat()
+ : playSynth(props.tockNotes, props.instrument))
);
onTock(
() =>
!muted.value &&
- (props.sound === "beep" ? playBeep(props.beepTock) : playTock())
+ (props.instrument === "click"
+ ? playTock()
+ : playSynth(props.tockNotes, props.instrument))
);
const emit = defineEmits(["didCreateInterval"]);
diff --git a/src/main.ts b/src/main.ts
index 8e33e03..49eca23 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -3,7 +3,10 @@ import Metronome from "./components/Metronome.vue";
import { App, createApp } from "vue";
import { Meter } from "./models/Meter";
import { MetronomeSize, isMetronomeSize } from "./models/MetronomeSize";
-import { MetronomeSound, isMetronomeSound } from "./models/MetronomeSound";
+import {
+ MetronomeInstrument,
+ isMetronomeInstrument,
+} from "./models/MetronomeInstrument";
import { MetronomeStyle, isMetronomeStyle } from "./models/MetronomeStyle";
import { Frequency } from "tone/build/esm/core/type/Units";
import { MetronomeBPM } from "./models/MetronomeBPM";
@@ -15,9 +18,9 @@ export interface MetronomeCodeBlockParameters {
size: MetronomeSize;
style: MetronomeStyle;
autoStart: boolean;
- sound: MetronomeSound;
- beepTick: Frequency[];
- beepTock: Frequency[];
+ instrument: MetronomeInstrument;
+ tickNotes: Frequency[];
+ tockNotes: Frequency[];
}
export default class MetronomePlugin extends Plugin {
@@ -82,11 +85,13 @@ export default class MetronomePlugin extends Plugin {
size: isMetronomeSize(values.size) ? values.size : "small",
style: isMetronomeStyle(values.style) ? values.style : "pulse",
autoStart: values.autostart ? values.autoStart === "yes" : null,
- sound: isMetronomeSound(values.sound) ? values.sound : "click",
- beepTick: parseMaybeCommaSeparatedToArray(values.beeptick) || [
+ instrument: isMetronomeInstrument(values.instrument)
+ ? values.instrument
+ : "click",
+ tickNotes: parseMaybeCommaSeparatedToArray(values.ticknotes) || [
"C6",
],
- beepTock: parseMaybeCommaSeparatedToArray(values.beeptock) || [
+ tockNotes: parseMaybeCommaSeparatedToArray(values.tocknotes) || [
"C5",
],
};
diff --git a/src/models/MetronomeInstrument.ts b/src/models/MetronomeInstrument.ts
new file mode 100644
index 0000000..56d2b4f
--- /dev/null
+++ b/src/models/MetronomeInstrument.ts
@@ -0,0 +1,41 @@
+export type MetronomeInstrument =
+ | "click"
+ | "beep"
+ | "AMSynth"
+ | "DuoSynth"
+ | "FMSynth"
+ | "MembraneSynth"
+ | "MetalSynth"
+ | "MonoSynth"
+ | "NoiseSynth"
+ | "PluckSynth"
+ | "Synth";
+
+export const defaultMetronomeInstrument = "beep";
+
+export function isMetronomeInstrument(inputInstrument: string): boolean {
+ return [
+ "click",
+ "beep",
+ "AMSynth",
+ "DuoSynth",
+ "FMSynth",
+ "MembraneSynth",
+ "MetalSynth",
+ "MonoSynth",
+ "PluckSynth",
+ ].includes(inputInstrument);
+}
+
+export const normalizeInstrumentNameForToneJS = (
+ instrument: MetronomeInstrument
+) => {
+ switch (instrument) {
+ case "click":
+ return "NoiseSynth";
+ case "beep":
+ return "Synth";
+ default:
+ return instrument;
+ }
+};
diff --git a/src/models/MetronomeSound.ts b/src/models/MetronomeSound.ts
deleted file mode 100644
index c561eaa..0000000
--- a/src/models/MetronomeSound.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-export type MetronomeSound = "click" | "beep";
-
-export function isMetronomeSound(
- inputSound: string
-): inputSound is MetronomeSound {
- return ["click", "beep"].includes(inputSound);
-}
diff --git a/src/sounds.ts b/src/sounds.ts
index e128ffd..4910569 100644
--- a/src/sounds.ts
+++ b/src/sounds.ts
@@ -1,7 +1,56 @@
import * as Tone from "tone/build/esm";
+import { PolySynth } from "tone/build/esm";
import { Frequency } from "tone/build/esm/core/type/Units";
+import {
+ Instrument,
+ InstrumentOptions,
+} from "tone/build/esm/instrument/Instrument";
+import {
+ MetronomeInstrument,
+ normalizeInstrumentNameForToneJS,
+} from "./models/MetronomeInstrument";
-const beepSynth = new Tone.PolySynth().toDestination();
+const synthCache: {
+ [key: string]: Tone.PolySynth | Instrument;
+} = {};
+const getSynth = (instrument: MetronomeInstrument) => {
+ if (!synthCache[instrument]) {
+ let synth;
+ switch (instrument) {
+ case "AMSynth":
+ synth = new Tone.PolySynth(Tone.AMSynth);
+ break;
+ case "DuoSynth":
+ synth = new Tone.PolySynth(Tone.DuoSynth);
+ break;
+ case "FMSynth":
+ synth = new Tone.PolySynth(Tone.FMSynth);
+ break;
+ case "MembraneSynth":
+ synth = new Tone.PolySynth(Tone.MembraneSynth);
+ break;
+ case "MetalSynth":
+ synth = new Tone.PolySynth(Tone.MetalSynth);
+ break;
+ case "MonoSynth":
+ synth = new Tone.PolySynth(Tone.MonoSynth);
+ break;
+ case "NoiseSynth":
+ synth = new Tone.NoiseSynth();
+ break;
+ case "PluckSynth":
+ synth = new Tone.PluckSynth();
+ break;
+ case "Synth":
+ default:
+ synth = new Tone.PolySynth(Tone.Synth);
+ break;
+ }
+ synthCache[instrument] = synth.toDestination();
+ }
+
+ return synthCache[instrument];
+};
const whiteNoise = new Tone.NoiseSynth({
noise: { type: "white" },
@@ -15,15 +64,28 @@ const pinkNoise = new Tone.NoiseSynth({
noise: { type: "pink" },
}).toDestination();
-export const tick = () => whiteNoise.triggerAttackRelease("8n");
-export const tock = () => brownNoise.triggerAttackRelease("8n");
-export const tickUpbeat = () => pinkNoise.triggerAttackRelease("8n");
-export const beep = (notes: Frequency[] = ["C4"]) => {
+export const playTick = () => whiteNoise.triggerAttackRelease("8n");
+export const playTock = () => brownNoise.triggerAttackRelease("8n");
+export const playTickUpbeat = () => pinkNoise.triggerAttackRelease("8n");
+export const playSynth = (
+ notes: Frequency[] = ["C4"],
+ instrument: MetronomeInstrument = "click"
+) => {
+ const synth = getSynth(normalizeInstrumentNameForToneJS(instrument));
+
try {
- beepSynth.triggerAttackRelease(notes, "16n");
+ if (synth instanceof PolySynth) {
+ synth.triggerAttackRelease(notes, "16n");
+ } else if (synth instanceof Instrument) {
+ // Mono synth
+ synth.triggerAttackRelease(notes[0], "16n");
+ }
} catch (e) {
// Probably invalid note value. Play no note instead.
- console.error(e);
- beepSynth.releaseAll();
+ if (synth instanceof PolySynth) {
+ synth.releaseAll();
+ } else if (synth instanceof Instrument) {
+ synth.triggerRelease();
+ }
}
};