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

MTS "Bulk Tuning Dump" exporter & Korg exporter refactoring #447

Merged
merged 1 commit into from
Nov 26, 2023
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
46 changes: 45 additions & 1 deletion src/assets/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@

--color-error: red;

/* Mimic Bootstrap alert with 'danger' variant */
--color-alert-danger: rgba(104, 35, 39, 1.0);
--color-alert-danger-background: rgba(243, 216, 218, 1.0);
--color-alert-danger-border: rgba(239, 199, 204, 1.0);

--section-gap: 160px;
--base-font-size: 15px;
--base-line-height: 1.5;
Expand Down Expand Up @@ -231,6 +236,9 @@ ul.btn-group, .btn-dropdown-group ul {
.control select {
flex-grow: 1;
}
.control input.half {
flex-grow: 0.25 !important;
}
.control.checkbox-container {
flex-flow: unset;
}
Expand All @@ -251,7 +259,7 @@ ul.btn-group, .btn-dropdown-group ul {
}
}

/* UI element - expandable secion */
/* UI element - expandable section */
.section::after {
content: " ▼";
font-size: 0.5rem;
Expand All @@ -274,3 +282,39 @@ p.section {
font-weight: bold;
cursor: pointer;
}

/* UI element - question mark with tooltip on hover */
span.info-question {
background: var(--color-background);
color: var(--color-text);
border-color: var(--color-text);
border-radius: 50%;
border-width: 2px;
border-style: solid;
padding-left: 4px;
padding-right: 4px;
font-size: smaller;

transition: 0.3s ease;
}
span.info-question::after {
content: '?';
}
span.info-question:hover {
background: var(--color-text);
color: var(--color-background);
transition: all 0.3s ease;
}

/* Padded box for displaying UI feedback message */
div.alert-box-danger {
background: var(--color-alert-danger-background);
border-width: 1px;
border-radius: 1%;
border-style: solid;
border-color: var(--color-alert-danger-border);
padding: 8px;
}
p.alert-message-danger {
color: var(--color-alert-danger);
}
43 changes: 33 additions & 10 deletions src/components/ScaleBuilder.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { APP_TITLE } from "@/constants";
import { sanitizeFilename, midiNoteNumberToName } from "@/utils";
import { exportFile, type ExporterKey } from "@/exporters";
import Modal from "@/components/ModalDialog.vue";
import ReaperExportModal from "@/components/modals/ReaperExport.vue";
import ReaperExportModal from "@/components/modals/export/ReaperExport.vue";
import MtsSysexExportModal from "@/components/modals/export/MtsSysexExport.vue";
import KorgExportModal from "@/components/modals/export/KorgExport.vue";
import ShareUrlModal from "@/components/modals/ShareUrl.vue";
import EqualTemperamentModal from "@/components/modals/generation/EqualTemperament.vue";
import HarmonicSeriesModal from "@/components/modals/generation/HarmonicSeries.vue";
Expand Down Expand Up @@ -136,7 +138,9 @@ function doExport(exporter: ExporterKey) {
exportFile(exporter, params);
}

const showKorgExportModal = ref(false);
const showReaperExportModal = ref(false);
const showMtsSysexExportModal = ref(false);
const showShareUrlModal = ref(false);
const shareUrlModal = ref<any>(null);

Expand Down Expand Up @@ -473,15 +477,10 @@ function copyToClipboard() {
<p><strong>Sytrus pitch map (.fnv)</strong></p>
<p>Envelope state file for the pitch envelope in Image-Line Sytrus</p>
</a>
<a href="#" class="btn" @click="doExport('mnlgtuns')">
<p><strong>Korg 'logue user scale (.mnlgtuns)</strong></p>
<p>Single scale for Korg 'logue Sound Librarian.</p>
<p>Full tuning of all MIDI notes</p>
</a>
<a href="#" class="btn" @click="doExport('mnlgtuno')">
<p><strong>Korg 'logue user octave (.mnlgtuno)</strong></p>
<p>Single scale for Korg 'logue Sound Librarian.</p>
<p>Only supports octave-repeating scales with 12 intervals</p>
<a href="#" class="btn" @click="showKorgExportModal = true">
<p><strong>Korg Sound Librarian scale (.mnlgtuns + others)</strong></p>
<p>Tuning formats for use with Monologue, Minilogue,</p>
<p>Minilogue XD, and Prologue synthesizers</p>
</a>
<a href="#" class="btn" @click="doExport('deflemask')">
<p><strong>Deflemask reference (.txt)</strong></p>
Expand All @@ -491,6 +490,10 @@ function copyToClipboard() {
<p><strong>Reaper note name map (.txt)</strong></p>
<p>Displays custom note names on Reaper's piano roll</p>
</a>
<a href="#" class="btn" @click="showMtsSysexExportModal = true">
<p><strong>MTS Sysex Bulk Tuning Dump (.syx)</strong></p>
<p>Binary data of a Bulk Tuning Dump SysEx message</p>
</a>
<a
href="#"
class="btn"
Expand Down Expand Up @@ -522,6 +525,16 @@ function copyToClipboard() {
/>

<Teleport to="body">
<KorgExportModal
:show="showKorgExportModal"
@confirm="showKorgExportModal = false"
@cancel="showKorgExportModal = false"
:newline="props.newline"
:scaleName="scaleName"
:baseMidiNote="baseMidiNote"
:scale="scale"
/>

<ReaperExportModal
:show="showReaperExportModal"
@confirm="showReaperExportModal = false"
Expand All @@ -532,6 +545,16 @@ function copyToClipboard() {
:scale="scale"
/>

<MtsSysexExportModal
:show="showMtsSysexExportModal"
@confirm="showMtsSysexExportModal = false"
@cancel="showMtsSysexExportModal = false"
:newline="props.newline"
:scaleName="scaleName"
:baseMidiNote="baseMidiNote"
:scale="scale"
/>

<RankTwoModal
:show="showRankTwoModal"
:centsFractionDigits="centsFractionDigits"
Expand Down
106 changes: 106 additions & 0 deletions src/components/modals/export/KorgExport.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<script setup lang="ts">
import { KorgModels, KorgExporter, getKorgModelInfo } from "@/exporters/korg";
import { sanitizeFilename } from "@/utils";
import { computed, ref } from "vue";
import Modal from "@/components/ModalDialog.vue";
import type { Scale } from "scale-workshop-core";

const props = defineProps<{
newline: string;
scaleName: string;
baseMidiNote: number;
scale: Scale;
}>();

const emit = defineEmits(["confirm", "cancel"]);

const models = [
KorgModels.MONOLOGUE,
KorgModels.MINILOGUE,
KorgModels.MINILOGUE_XD,
KorgModels.PROLOGUE,
];

const modelName = ref("minilogue");
const useOctaveFormat = ref(false);

const dialogErrorMessage = computed(() => {
if (useOctaveFormat.value) {
const message = KorgExporter.getOctaveFormatErrorMessage(props.scale);
if (message.length > 0) return message;
}
// Can check for other errors here...
return String();
});

const fileTypePreview = computed(() => {
const format = getKorgModelInfo(modelName.value);
return useOctaveFormat.value ? format.octave : format.scale;
});

async function doExport() {
const params = {
newline: props.newline,
scale: props.scale,
filename: sanitizeFilename(props.scaleName),
baseMidiNote: props.baseMidiNote,
};

const exporter = new KorgExporter(
params,
modelName.value,
useOctaveFormat.value
);
await exporter.saveFile();

emit("confirm");
}
</script>

<template>
<Modal>
<template #header>
<h2>Export Korg Sound Librarian scale</h2>
</template>
<template #body>
<div class="control-group">
<div class="control">
<label for="modelName">Synth Model</label>
<select id="modelName" v-model="modelName">
<option v-for="name of models" :key="name" :value="name">
{{ getKorgModelInfo(name).title }}
</option>
</select>
</div>
<div id="format" class="control radio-group">
<label for="format">Tuning Format</label>
<label>
<input type="radio" :value="false" v-model="useOctaveFormat" />
&nbsp;Scale (128-note table)
</label>
<label>
<input type="radio" :value="true" v-model="useOctaveFormat" />
&nbsp;Octave (12-note table, octave repeating with fixed C)
</label>
</div>
<p>
<label>Export Format: </label>
{{ fileTypePreview }}
</p>
<div class="alert-box-danger" v-if="dialogErrorMessage.length > 0">
<p class="alert-message-danger">
{{ dialogErrorMessage }}
</p>
</div>
</div>
</template>
<template #footer>
<div class="btn-group">
<button @click="doExport" :disabled="dialogErrorMessage.length > 0">
OK
</button>
<button @click="$emit('cancel')">Cancel</button>
</div>
</template>
</Modal>
</template>
105 changes: 105 additions & 0 deletions src/components/modals/export/MtsSysexExport.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<script setup lang="ts">
import MtsSysexExporter from "@/exporters/mts-sysex";
import { sanitizeFilename } from "@/utils";
import { ref, watch } from "vue";
import Modal from "@/components/ModalDialog.vue";
import type { Scale } from "scale-workshop-core";
import { clamp } from "xen-dev-utils";

const props = defineProps<{
newline: string;
scaleName: string;
baseMidiNote: number;
scale: Scale;
}>();

const emit = defineEmits(["confirm", "cancel"]);

// Rarely implemented parameters
// const deviceId = ref(0);
// const bank = ref(0); (only for message 0x0804)

function clampName(name: string): string {
return name.slice(0, 16);
}

const name = ref(clampName(props.scaleName));

function nameInputCallback(nameInput: string): void {
name.value = clampName(nameInput);
}

watch(
() => props.scaleName,
(newName) => nameInputCallback(newName),
{ immediate: true }
);

function formatPresetIndex(input: string): string {
const number = parseInt(input.replace(/\D/g, ""));
if (Number.isNaN(number)) return "0";
return String(clamp(0, 127, number));
}

const presetIndex = ref("0");

function presetIndexInputCallback(indexInput: string): void {
presetIndex.value = formatPresetIndex(indexInput);
}

function doExport() {
const params = {
newline: props.newline,
scale: props.scale,
filename: sanitizeFilename(props.scaleName),
baseMidiNote: props.baseMidiNote,
name: name.value,
presetIndex: parseInt(presetIndex.value),
};

const exporter = new MtsSysexExporter(params);
exporter.saveFile();

emit("confirm");
}
</script>

<template>
<Modal @confirm="doExport" @cancel="$emit('cancel')">
<template #header>
<h2>Export MTS Bulk Tuning Dump</h2>
</template>
<template #body>
<div class="control-group">
<div class="control">
<label for="name">Name (16-character limit)</label>
<input
class="half"
type="text"
id="name"
v-model="name"
@input="nameInputCallback(name)"
/>
</div>
<div class="control">
<label for="preset-index">
Preset Index&nbsp;
<span
@click="$event.preventDefault()"
class="info-question"
title="Refer to your synth's manual for a valid range"
>
</span>
</label>
<input
class="half"
id="preset-index"
type="text"
v-model="presetIndex"
@input="presetIndexInputCallback(presetIndex)"
/>
</div>
</div>
</template>
</Modal>
</template>
12 changes: 0 additions & 12 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,5 @@ export const WINDOWS_NEWLINE = "\r\n";

export const NUMBER_OF_NOTES = 128;

// Korg specific constants for exporters and importers
export const KORG = {
programmer: "ScaleWorkshop",
mnlg: {
octaveSize: 12,
scaleSize: 128,
maxCents: 12800,
refA: { val: 6900, ind: 69, freq: 440.0 },
refC: { val: 6000, ind: 60, freq: 261.6255653 },
},
};

// Browser interaction
export const LEFT_MOUSE_BTN = 0;
Loading