From dfd08d55f844d793b8a982c3ed75af5f77b6875a Mon Sep 17 00:00:00 2001 From: Max Alyokhin <55048182+MaxAlyokhin@users.noreply.github.com> Date: Tue, 12 Sep 2023 00:01:40 +0300 Subject: [PATCH] translate comments and bugfixes --- README.md | 4 + README_RU.md | 53 +++++++---- dist/index.html | 6 +- src/BinarySynth.vue | 17 ++++ src/assets/js/getMIDINote.js | 14 +-- src/assets/js/helpers.js | 6 +- src/components/ControlPanel.vue | 102 +++++++++++----------- src/components/ControlPanel/Frequency.vue | 2 +- src/components/ControlPanel/Global.vue | 2 +- src/components/FileInput.vue | 38 +++++++- src/components/Status.vue | 22 ++--- 11 files changed, 168 insertions(+), 98 deletions(-) diff --git a/README.md b/README.md index 3b45e6e..77813fe 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,12 @@ _Binary file interpreter for audio synthesis_ +[![Uptime Robot status](https://img.shields.io/uptimerobot/status/m795264551-bb4c959b31b6ff94b02f9545)](https://bs.stranno.su) [![Uptime Robot status](https://img.shields.io/uptimerobot/ratio/m795264551-bb4c959b31b6ff94b02f9545)](https://bs.stranno.su) + **Demo**: https://bs.stranno.su +![](https://store.stranno.su/bs/design.png) + _Эта страница есть также на русском_ All data on any computer or smartphone is in the form of files. The contents of these files are ultimately just zeros and ones. And these zeros and ones are basically all the same, so we need an interpreter to extract meaning from these texts. Basically, the file format (.mp3, .docx, etc.) is just a pointer to which interpreter we need to pass the text in order to extract meaning from it. diff --git a/README_RU.md b/README_RU.md index 4ff86da..74c6b3f 100644 --- a/README_RU.md +++ b/README_RU.md @@ -2,8 +2,12 @@ _Интерпретатор двоичных файлов для аудио-синтеза_ +[![Uptime Robot status](https://img.shields.io/uptimerobot/status/m795264551-bb4c959b31b6ff94b02f9545)](https://bs.stranno.su) [![Uptime Robot status](https://img.shields.io/uptimerobot/ratio/m795264551-bb4c959b31b6ff94b02f9545)](https://bs.stranno.su) + **Демо**: https://bs.stranno.su +![](https://store.stranno.su/bs/design.png) + Все данные на любом компьютере или смартфоне представлены в виде файлов. Содержанием этих файлов в конечном итоге являются просто нули и единицы. И эти нули и единицы, в общем-то, все одинаковые, поэтому нам нужен интерпретатор, для того чтобы извлечь смысл из этих текстов. По сути, формат файла (.mp3, .docx и т.д.) это просто указатель, какому интерпретатору надо передать текст, чтобы из него извлечь смысл. Но что, если формат файла и интерпретатор не совпадают? Что касается музыкальных экспериментов, то ранее были, например, попытки "воспроизвести" текстовый или иной файл через аудио-редактор, что ожидаемо рождало в результате в основном глитч и нойз; это может быть интересно больше с концептуальной, чем с музыкальной точки зрения. @@ -22,7 +26,8 @@ _Интерпретатор двоичных файлов для аудио-си 3. На уровне всей системы мы задаём глобальные параметры: - скорость интерпретации -- музыкальный строй (или его отсутствие), диапазон нот/частот +- наличие случайной величины разброса скорости интерпретации +- музыкальный строй (или его отсутствие), диапазон нот/частот; по этому диапазону равномерно сопоставляются частоты по 256 или 65 536 возможным комбинациям нулей и единиц - зацикленность воспроизведения - режим MIDI - плавный или резкий переход между командами @@ -34,27 +39,19 @@ _Интерпретатор двоичных файлов для аудио-си 6. Если дошли до конца файла, прекращаем исполнение, либо начинаем заново -## Запуск локально и сборка проекта - -### Просто скопировать приложение - -Всё необходимое для работы системы заложено в единственный `.html` файл, который можно скачать в папке `dist`, либо просто перейти на https://bs.strannо.su и, нажав правую кнопку мыши, в меню выбрать Сохранить как. +## MIDI -### Собрать билд локально для доработки кода +При включении MIDI-режима автоматически выбирается первый попавшийся порт из доступных и его первый канал. Далее последовательно при чтении посылается сигнал noteOn, через время Reading speed посылается сигнал noteOff. В Continuous режиме после каждого noteOn посылается Pitch сигнал, чтобы попасть в нужную частоту. -Tech stack: Vue3 + Pinia + Vite. +MIDI-сообщения могут посылаться: -1. Скачать и установить LTS версию Node.js -2. Скачать код напрямую с Github, либо через `git clone` -3. В папке с проектом в терминале выполнить: +- в соседние вкладки и окна браузеров, если они слушают MIDI (например, в веб-аналог [DX7](http://mmontag.github.io/dx7-synth-js)) +- в DAW и прочие приложения, где есть виртуальные синтезаторы (то есть BS может управлять, например, синтезатором в Ableton) +- во внешние устройства, поддерживающие MIDI и подключённые к компьютеру -```bash -npm i -npm run dev # development-сборка -npm run build # production-сборка, генерирует index.html со всем необходимым -``` +> **Note**: После любых манипуляций в MIDI-портами (подключение/отключение/переподключение) необходимо полностью перезапустить браузер, закрыв все окна браузера если их несколько -Для тестов MIDI можно пользоваться этим ресурсом https://studiocode.dev/midi-monitor/ +> **Note**: MIDI-сообщения генерируются только на десктопе ## Особенности интерфейса @@ -76,3 +73,25 @@ npm run build # production-сборка, генерирует index.html со в - Random time gap - добавление случайной величины времени до следующего звука в пределах параметра Reading speed. Делает звук менее "роботизированным", так как расстояние до каждого звука немного отличается и это добавляет больше "живости" игре - Commands range - позволяет играть не весь файл, а его определённую часть + +## Запуск локально и сборка проекта + +### Просто скопировать приложение + +Всё необходимое для работы системы заложено в единственный `.html` файл, который можно скачать в папке `dist`, либо просто перейти на https://bs.strannо.su и, нажав правую кнопку мыши, в меню выбрать Сохранить как. + +### Собрать билд локально для доработки кода + +Tech stack: Vue3 + Pinia + Vite. + +1. Скачать и установить LTS версию Node.js +2. Скачать код напрямую с Github, либо через `git clone` +3. В папке с проектом в терминале выполнить: + +```bash +npm i +npm run dev # development-сборка +npm run build # production-сборка, генерирует index.html со всем необходимым +``` + +Для тестов MIDI можно пользоваться этим ресурсом https://studiocode.dev/midi-monitor/ diff --git a/dist/index.html b/dist/index.html index f04685e..bd37788 100644 --- a/dist/index.html +++ b/dist/index.html @@ -35,15 +35,15 @@ href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAABCFBMVEUAAAANERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcNERcLDxUKDhQMEBY/REdobm9YXl8dISYiJytkamtiaGkgJCl4fn6EiomLkZBqcHFyeXmIj453fX1NU1WSmZeVnJpVWlxhZ2ico6B8goJ6gIBzeXkkKC1QVlhtc3MLDxaLkpBwdnZjaGl+hISPlZRqcHA2Oj5LUFMZHSMfIyhWXF5SV1kaHiT///9VYDZ7AAAAKnRSTlMAAAZHpuP75KhJBxiR7u+TGbCyBZRI7Uuq4uX6/P3mp6tNlrQa8ErnTAjc5selAAAAAWJLR0RXfQrZHwAAAAd0SU1FB+cJBRUNJ/cSuWQAAADLSURBVBjTVY95c8EAEMV304hKiCCOqriPdomradUZtLRuqnz/j2Ix43gz+8f7zdsLAFB4EB2S5Hx0yQhsUXF76CTVqyECKj66yK8hBNx0I12AYIioWCqZJheRJwwikVmuVGv1N+udSQSeOPDR+Gy22p1ukSgKzww6Pbs/GFp1TsRAOoKv79H4x/6dEBngOIJpezZfLFcM4pDgoevq5m/7v9tzSxJcd2vVFAje28PSMqDmv/pMln9BTVfPNpfO4ulfOR+JG0Yh+fLK9gCSyCS/pajeSAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMy0wOS0wNVQyMToxMzozOSswMDowMJyosKIAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjMtMDktMDVUMjE6MTM6MzkrMDA6MDDt9QgeAAAAV3pUWHRSYXcgcHJvZmlsZSB0eXBlIGlwdGMAAHic4/IMCHFWKCjKT8vMSeVSAAMjCy5jCxMjE0uTFAMTIESANMNkAyOzVCDL2NTIxMzEHMQHy4BIoEouAOoXEXTyQjWVAAAAAElFTkSuQmCC" /> diff --git a/src/BinarySynth.vue b/src/BinarySynth.vue index 1062b0d..2a047af 100644 --- a/src/BinarySynth.vue +++ b/src/BinarySynth.vue @@ -13,6 +13,9 @@ const file = useFileStore()
+
@@ -37,5 +40,19 @@ const file = useFileStore() opacity: 0; position: fixed; } + + .about { + padding-top: 10px; + text-align: center; + + a { + color: rgb(101 106 113); + text-decoration: none; + + &:hover { + text-decoration: underline; + } + } + } } diff --git a/src/assets/js/getMIDINote.js b/src/assets/js/getMIDINote.js index 5780fcc..e8cf6a9 100644 --- a/src/assets/js/getMIDINote.js +++ b/src/assets/js/getMIDINote.js @@ -18,18 +18,18 @@ function getNearbyValues(number, array) { return [nearbyLess, nearbyOver] } -// Вычисляет массив: номер ноты и, при непрерывном режиме, величину питча +// Calculates an array of: note number and, in continuous mode, pitch value let frequency = null let nearbyValues = null let percent = null let pitchValue = null export function getMIDINote(byte, bitness, mode, coefficients, minimumFrequency, minimumNote) { - // Возвращается номер ноты + питч - // 1. Вычислить частоту - // 2. Найти ближайщую нижнюю ноту в массиве к этой частоте - // 3. Вычислить разницу между этой нотой и исходной частотой - // 4. Эту разницу перевести в величину питча + // Note number + pitch is returned + // 1. Calculate frequency + // 2. Find the nearest lower note in the array to this frequency + // 3. Calculate the difference between this note and the original frequency + // 4. Convert this difference into a pitch value if (mode === 'continuous') { // 1. if (byte === 0) frequency = minimumFrequency @@ -55,7 +55,7 @@ export function getMIDINote(byte, bitness, mode, coefficients, minimumFrequency, } } - // Возвращается номер ноты + // The note number returned if (mode === 'tempered') { if (bitness === '8') return [Math.floor(coefficients.tempered8 * byte) + minimumNote] if (bitness === '16') return [Math.floor(coefficients.tempered16 * byte) + minimumNote] diff --git a/src/assets/js/helpers.js b/src/assets/js/helpers.js index 6341eb5..ea5c7cc 100644 --- a/src/assets/js/helpers.js +++ b/src/assets/js/helpers.js @@ -20,9 +20,9 @@ export function getRandomNumber(min, max) { } /** - * Преобразовывает строки 'true' || 'false' в соответствующее булево значение - * @param {String} value - строка для преобразования - * @return {Boolean} Возвращает булево значение + * Converts 'true' || 'false' strings to the corresponding boolean value + * @param {String} value - conversion string + * @return {Boolean} Returns a boolean value */ export function getBooleanFromString(value) { // prettier-ignore diff --git a/src/components/ControlPanel.vue b/src/components/ControlPanel.vue index cec3348..38dff3d 100644 --- a/src/components/ControlPanel.vue +++ b/src/components/ControlPanel.vue @@ -22,7 +22,7 @@ const iterationTime = computed(() => ) const bynaryInSelectedBitness = computed(() => (settings.bitness === '8' ? file.binary8 : file.binary16)) -// Создание +// Creating let audioContext = new AudioContext() let oscillator = null let gain = audioContext.createGain() @@ -31,7 +31,7 @@ let lfoDepth = audioContext.createGain() let lfoOsc = audioContext.createOscillator() let masterGain = audioContext.createGain() -// Настройка +// Setup filter.type = 'lowpass' filter.frequency.value = settings.biquadFilterFrequency filter.Q.value = settings.biquadFilterQ @@ -42,7 +42,7 @@ lfoDepth.gain.value = settings.LFO.depth gain.gain.value = settings.gain masterGain.gain.value = 1 -// Соединение +// Connection filter.connect(gain) lfoDepth.connect(masterGain.gain) gain.connect(masterGain) @@ -57,8 +57,8 @@ const sawtoothWave = audioContext.createPeriodicWave( Float32Array.from(fourierCoefficients.sawtooth.imag) ) -// При каждом play создаём новый осциллятор и подключаем его -// Изменение его частоты будет планироваться в nextIteration() +// At each play, create a new oscillator and connect it +// Changing its frequency will be planned in nextIteration() function audioInit() { oscillator = audioContext.createOscillator() oscillator.type = settings.waveType @@ -69,12 +69,12 @@ audioInit() const getRandomTimeGap = () => (settings.isRandomTimeGap ? getRandomNumber(0, settings.readingSpeed) : 0) -// Чтобы снизить нагрузку на процессор, мы делим планирование композиции на итерации +// To reduce CPU overhead, we divide composition planning into iterations let nextIterationTimeoutID = null let midiTimeoutIDs = [] let commands = [] function nextIteration(iterationNumber, scheduledCommands) { - // Если дошли до конца команд или нажали stop, то выходим из рекурсии + // If we have reached the end of the commands or pressed stop, we exit the recursion if (!status.playing) { stop() return @@ -99,7 +99,7 @@ function nextIteration(iterationNumber, scheduledCommands) { } } - // Определяем блок команд (500 штук) + // Define block of commands (500 pieces) // prettier-ignore const end = scheduledCommands + fileReadingLimit.value < settings.commandsRange.to ? scheduledCommands + fileReadingLimit.value - 1 @@ -108,13 +108,13 @@ function nextIteration(iterationNumber, scheduledCommands) { status.currentCommandsBlock = [scheduledCommands, end] status.iterationNumber = iterationNumber - // Планируем композицию + // Planning the composition let command = null - // noteIndex - порядковый номер элемента во всём массиве команд - // index - порядковый номер элемента в контексте итерации + // noteIndex - serial number of the element in the whole command array + // index - sequence number of the element in the iteration context - // Для обычного режима + // For normal mode if (!settings.midiMode) { switch (settings.transitionType) { @@ -128,7 +128,7 @@ function nextIteration(iterationNumber, scheduledCommands) { settings.frequenciesRange.from, settings.notesRange.from ) - if (!isFinite(command)) command = 0 // На больших readingSpeed бывают глюки + if (!isFinite(command)) command = 0 // There are glitches on large readingSpeeds oscillator.frequency.setValueAtTime( command, @@ -147,7 +147,7 @@ function nextIteration(iterationNumber, scheduledCommands) { settings.frequenciesRange.from, settings.notesRange.from ) - if (!isFinite(command)) command = 0 // На больших readingSpeed бывают глюки + if (!isFinite(command)) command = 0 // There are glitches on large readingSpeeds oscillator.frequency.linearRampToValueAtTime( command, audioContext.currentTime + (index * settings.readingSpeed + getRandomTimeGap()) @@ -165,7 +165,7 @@ function nextIteration(iterationNumber, scheduledCommands) { settings.frequenciesRange.from, settings.notesRange.from ) - if (!isFinite(command)) command = 0.01 // На больших readingSpeed бывают глюки + if (!isFinite(command)) command = 0.01 // There are glitches on large readingSpeeds oscillator.frequency.exponentialRampToValueAtTime( command, audioContext.currentTime + (index * settings.readingSpeed + getRandomTimeGap()) @@ -174,7 +174,7 @@ function nextIteration(iterationNumber, scheduledCommands) { break } } - // Для MIDI-режима + // For MIDI-mode else { midiTimeoutIDs.forEach((id) => { clearTimeout(id) @@ -208,11 +208,11 @@ function nextIteration(iterationNumber, scheduledCommands) { } } - // Если в файле байтов меньше, чем fileReadingLimit.value, то рекурсия отменяется + // If there are fewer bytes in the file than fileReadingLimit.value, recursion is canceled if (fileReadingLimit.value >= settings.commandsRange.to - settings.commandsRange.from) { if (!settings.loop) { nextIterationTimeoutID = setTimeout(() => { - // Чтобы последняя нота не затягивалась + // So that the last note doesn't take too long if (settings.midiMode) { sendMIDIMessage.noteOff( commands[settings.commandsRange.to - settings.commandsRange.from][0], @@ -225,7 +225,7 @@ function nextIteration(iterationNumber, scheduledCommands) { }, (settings.commandsRange.to - settings.commandsRange.from + 1) * settings.readingSpeed * 1000) } else { nextIterationTimeoutID = setTimeout(() => { - // Чтобы последняя нота не затягивалась + // So that the last note doesn't take too long if (settings.midiMode) { sendMIDIMessage.noteOff( commands[settings.commandsRange.to - settings.commandsRange.from][0], @@ -239,7 +239,7 @@ function nextIteration(iterationNumber, scheduledCommands) { } } else { nextIterationTimeoutID = setTimeout(() => { - // Чтобы последняя нота не затягивалась + // So that the last note doesn't take too long if (settings.midiMode) { sendMIDIMessage.noteOff(commands[commands.length - 1][0], settings.midi.velocity, settings.midi.port, settings.midi.channel) } @@ -314,7 +314,7 @@ const gainValue = computed(() => settings.gain) watch(gainValue, (newValue) => { gain.gain.value = newValue }) -// При изменении типа волны в UI сразу передаём его в осциллятор +// When we change the wave type in the UI, we immediately pass it to the oscillator const wave = computed(() => settings.waveType) watch(wave, (newValue) => { if (newValue === 'square2') { @@ -325,7 +325,7 @@ watch(wave, (newValue) => { oscillator.type = newValue } }) -// По окончании композиции заново создаём связку +// At the end of the composition, re-create the binder const playing = computed(() => status.playing) watch(playing, (newValue) => { if (newValue === false) { @@ -342,7 +342,7 @@ const frequencyCoefficients = computed(() => { } }) -// При изменении этих параметров полностью заново пересчитать планирование +// If these parameters are changed, completely recalculate the scheduling again const readingSpeed = computed(() => settings.readingSpeed) const transitionType = computed(() => settings.transitionType) const isRandomTimeGap = computed(() => settings.isRandomTimeGap) @@ -350,13 +350,13 @@ watch([readingSpeed, transitionType, isRandomTimeGap], () => { if (status.playing) { let command = null - clearTimeout(nextIterationTimeoutID) // Отменяем рекурсию + clearTimeout(nextIterationTimeoutID) // Cancel the recursion if (!settings.midiMode) { - // Отменяем уже запланированное для осциллятора + // Cancel already planned for the oscillator oscillator.frequency.cancelScheduledValues(audioContext.currentTime) - // Перепланируем изменения в осцилляторе, начиная с последней команды на которой остановились + // Reschedule the changes in the oscillator, starting from the last command where we left off switch (settings.transitionType) { case 'immediately': for ( @@ -373,7 +373,7 @@ watch([readingSpeed, transitionType, isRandomTimeGap], () => { settings.notesRange.from ) - if (!isFinite(command)) command = 0 // На больших readingSpeed бывают глюки + if (!isFinite(command)) command = 0 // There are glitches on large readingSpeeds oscillator.frequency.setValueAtTime( command, @@ -397,7 +397,7 @@ watch([readingSpeed, transitionType, isRandomTimeGap], () => { settings.notesRange.from ) - if (!isFinite(command)) command = 0 // На больших readingSpeed бывают глюки + if (!isFinite(command)) command = 0 // There are glitches on large readingSpeeds oscillator.frequency.linearRampToValueAtTime( command, audioContext.currentTime + (index * settings.readingSpeed + getRandomTimeGap()) @@ -420,7 +420,7 @@ watch([readingSpeed, transitionType, isRandomTimeGap], () => { settings.notesRange.from ) - if (!isFinite(command)) command = 0.01 // На больших readingSpeed бывают глюки + if (!isFinite(command)) command = 0.01 // There are glitches on large readingSpeeds oscillator.frequency.exponentialRampToValueAtTime( command, audioContext.currentTime + (index * settings.readingSpeed + getRandomTimeGap()) @@ -471,12 +471,12 @@ watch([readingSpeed, transitionType, isRandomTimeGap], () => { } } - // Заново планируем рекурсию - // Время следующей рекурсии это количество оставшихся в итерации команд * settings.readingSpeed + // Reschedule the recursion + // The time of the next recursion is the number of commands remaining in the iteration * settings.readingSpeed if (fileReadingLimit.value >= settings.commandsRange.to - settings.commandsRange.from) { if (!settings.loop) { nextIterationTimeoutID = setTimeout(() => { - // Чтобы последняя нота не затягивалась + // So that the last note doesn't take too long if (settings.midiMode) { sendMIDIMessage.noteOff( commands[settings.commandsRange.to - settings.commandsRange.from][0], @@ -489,7 +489,7 @@ watch([readingSpeed, transitionType, isRandomTimeGap], () => { }, (settings.commandsRange.to - settings.commandsRange.from - status.currentCommand) * settings.readingSpeed * 1000) } else { nextIterationTimeoutID = setTimeout(() => { - // Чтобы последняя нота не затягивалась + // So that the last note doesn't take too long if (settings.midiMode) { sendMIDIMessage.noteOff( commands[settings.commandsRange.to - settings.commandsRange.from][0], @@ -521,7 +521,7 @@ watch([readingSpeed, transitionType, isRandomTimeGap], () => { } }) -// При изменении этих параметров пересчитать только частоты +// When changing these parameters, only the frequencies are recalculated const frequenciesRange = computed(() => settings.frequenciesRange) const notesRange = computed(() => settings.notesRange) const frequencyMode = computed(() => settings.frequencyMode) @@ -530,10 +530,10 @@ watch([frequenciesRange.value, notesRange.value, frequencyMode], () => { let command = null if (!settings.midiMode) { - // Отменяем уже запланированное для осциллятора + // Cancel already planned for the oscillator oscillator.frequency.cancelScheduledValues(audioContext.currentTime) - // Перепланируем изменения в осцилляторе, начиная с последней команды на которой остановились + // Reschedule the changes in the oscillator, starting from the last command where we left off switch (settings.transitionType) { case 'immediately': for ( @@ -549,7 +549,7 @@ watch([frequenciesRange.value, notesRange.value, frequencyMode], () => { settings.frequenciesRange.from, settings.notesRange.from ) - if (!isFinite(command)) command = 0 // На больших readingSpeed бывают глюки + if (!isFinite(command)) command = 0 // There are glitches on large readingSpeeds oscillator.frequency.setValueAtTime(command, audioContext.currentTime + index * settings.readingSpeed) } break @@ -568,7 +568,7 @@ watch([frequenciesRange.value, notesRange.value, frequencyMode], () => { settings.frequenciesRange.from, settings.notesRange.from ) - if (!isFinite(command)) command = 0 // На больших readingSpeed бывают глюки + if (!isFinite(command)) command = 0 // There are glitches on large readingSpeeds oscillator.frequency.linearRampToValueAtTime(command, audioContext.currentTime + index * settings.readingSpeed) } break @@ -587,7 +587,7 @@ watch([frequenciesRange.value, notesRange.value, frequencyMode], () => { settings.frequenciesRange.from, settings.notesRange.from ) - if (!isFinite(command)) command = 0.01 // На больших readingSpeed бывают глюки + if (!isFinite(command)) command = 0.01 // There are glitches on large readingSpeeds oscillator.frequency.exponentialRampToValueAtTime(command, audioContext.currentTime + index * settings.readingSpeed) } break @@ -637,7 +637,7 @@ watch([frequenciesRange.value, notesRange.value, frequencyMode], () => { } }) -// При изменении этих параметров начать игру заново +// If you change these parameters, start the game over again const bitness = computed(() => settings.bitness) const commandsRange = computed(() => settings.commandsRange) watch([bitness, commandsRange.value], () => { @@ -658,15 +658,15 @@ watch(midiMode, (newValue) => { audioInit() } - // Если перешли на MIDI-режим, то гасим осциллятор + // If the user has switched to MIDI mode, cancel the oscillator if (playing.value) { let command = null - // Если включили MIDI + // If MIDI is on if (newValue === true) { oscillator.stop(audioContext.currentTime) oscillator.frequency.cancelScheduledValues(audioContext.currentTime) - clearTimeout(nextIterationTimeoutID) // Отменяем запланированную рекурсию + clearTimeout(nextIterationTimeoutID) // Cancel the scheduled recursion for ( let noteIndex = status.currentCommandsBlock[0] + status.currentCommand, index = 0; @@ -704,18 +704,18 @@ watch(midiMode, (newValue) => { ) } } - // Если отключили MIDI + // If MIDI is off else { midiTimeoutIDs.forEach((id) => { clearTimeout(id) }) sendMIDIMessage.allSoundOff(settings.midi.port, settings.midi.channel) - clearTimeout(nextIterationTimeoutID) // Отменяем запланированную рекурсию + clearTimeout(nextIterationTimeoutID) // Cancel the scheduled recursion oscillator.start() - // Перепланируем изменения в осцилляторе, начиная с последней команды на которой остановились + // Reschedule the changes in the oscillator, starting from the last command where we left off switch (settings.transitionType) { case 'immediately': for ( @@ -732,7 +732,7 @@ watch(midiMode, (newValue) => { settings.notesRange.from ) - if (!isFinite(command)) command = 0 // На больших readingSpeed бывают глюки + if (!isFinite(command)) command = 0 // There are glitches on large readingSpeeds oscillator.frequency.setValueAtTime(command, audioContext.currentTime + index * settings.readingSpeed) } break @@ -752,7 +752,7 @@ watch(midiMode, (newValue) => { settings.notesRange.from ) - if (!isFinite(command)) command = 0 // На больших readingSpeed бывают глюки + if (!isFinite(command)) command = 0 // There are glitches on large readingSpeeds oscillator.frequency.linearRampToValueAtTime(command, audioContext.currentTime + index * settings.readingSpeed) } break @@ -772,15 +772,15 @@ watch(midiMode, (newValue) => { settings.notesRange.from ) - if (!isFinite(command)) command = 0.01 // На больших readingSpeed бывают глюки + if (!isFinite(command)) command = 0.01 // There are glitches on large readingSpeeds oscillator.frequency.exponentialRampToValueAtTime(command, audioContext.currentTime + index * settings.readingSpeed) } break } } - // Заново планируем рекурсию - // Время следующей рекурсии это количество оставшихся в итерации команд * settings.readingSpeed + // Reschedule the recursion + // The time of the next recursion is the number of commands remaining in the iteration * settings.readingSpeed if (fileReadingLimit.value >= settings.commandsRange.to) { if (!settings.loop) { nextIterationTimeoutID = setTimeout(() => { diff --git a/src/components/ControlPanel/Frequency.vue b/src/components/ControlPanel/Frequency.vue index 2d605ff..7c65551 100644 --- a/src/components/ControlPanel/Frequency.vue +++ b/src/components/ControlPanel/Frequency.vue @@ -21,7 +21,7 @@ watch(frequencyFrom, (newValue) => { const frequencyTo = ref(settings.frequenciesRange.to) watch(frequencyTo, (newValue) => { - // setTimeout чтобы эта проверка сработала после frequencyFrom + // setTimeout to make this check triggered after frequencyFrom setTimeout(() => { if (isNaN(newValue)) { return diff --git a/src/components/ControlPanel/Global.vue b/src/components/ControlPanel/Global.vue index 1bd31da..e606923 100644 --- a/src/components/ControlPanel/Global.vue +++ b/src/components/ControlPanel/Global.vue @@ -55,7 +55,7 @@ watch(commandsFrom, (newValue) => { const commandsTo = ref(settings.commandsRange.to) watch(commandsTo, (newValue) => { - // setTimeout чтобы эта проверка сработала после commandsFrom + // setTimeout to make this check triggered after frequencyFrom setTimeout(() => { if (isNaN(newValue)) { return diff --git a/src/components/FileInput.vue b/src/components/FileInput.vue index bb35bec..318241b 100644 --- a/src/components/FileInput.vue +++ b/src/components/FileInput.vue @@ -2,8 +2,8 @@ import { ref, onMounted, onBeforeMount } from 'vue' import { useFileStore, useStatusStore } from '@/stores/global.js' -// Принимает файл -// Пишет в стор представления файла и информацию о нём +// Receives the file +// Writes the file representation and information about the file to the store const reader = new FileReader() const file = useFileStore() const status = useStatusStore() @@ -28,8 +28,8 @@ reader.addEventListener('loadend', async (event) => { status.currentCommandsBlock = [0, 499] } - // Для файлов с нечётным количеством байт нельзя создать Uint16Array - // Поэтому мы можем заполнить недостающее нулями + // For files with an odd number of bytes we cannot create a Uint16Array + // So we can fill the missing with zeros let binary8 = new Uint8Array(event.target.result) let binary16 = null @@ -131,6 +131,10 @@ onMounted(() => {
CLICK FOR UPLOAD FILE
OR DROP FILE HERE
+ +
+ About and sources +
@@ -191,17 +195,43 @@ onMounted(() => { .title { font-size: 6vw; + + @media (max-width: 768px) { + font-size: calc(0.525em + 6vw); + } } .description { text-transform: uppercase; font-size: 2vw; font-style: italic; + + @media (max-width: 768px) { + font-size: calc(0.325em + 2vw); + } } .cta { font-size: 2vw; margin-top: 50px; + + @media (max-width: 768px) { + font-size: calc(0.325em + 2vw); + } + } +} + +.about-on-first-screen { + position: absolute; + bottom: 10px; + + a { + text-decoration: none; + color: rgb(101 106 113); + + &:hover { + text-decoration: underline; + } } } diff --git a/src/components/Status.vue b/src/components/Status.vue index 14923a5..536d191 100644 --- a/src/components/Status.vue +++ b/src/components/Status.vue @@ -24,16 +24,16 @@ function timer() { }, 1000) } -// Подсветка текущей команды в UI +// Highlighting the current command in the UI let commandIteratorInterval = null let currentIteration = 0 -// По каждому play создаём новый итератор +// Create a new iterator for each play function commandIterator() { - // Максимальная скорость setInterval 5мс + // Maximum speed of setInterval 5ms if (readingSpeed.value >= 0.005) { return setInterval(() => { - // Если у нас один лист + // If we have one sheet if (settings.commandsRange.to - settings.commandsRange.from <= commandsOnList.value) { if (status.currentCommand >= settings.commandsRange.to - settings.commandsRange.from) { status.currentCommand = 0 @@ -41,8 +41,8 @@ function commandIterator() { status.currentCommand++ } } - // Если несколько листов - // Мы можем определить переход на следующую порцию команд при изменении status.iterationNumber + // If multiple sheets + // We can define the transition to the next instruction portion when status.iterationNumber changes else { if (currentIteration !== status.iterationNumber) { currentIteration = status.iterationNumber @@ -53,9 +53,9 @@ function commandIterator() { } }, readingSpeed.value * 1000) } else { - // Если скорость большая, то отображаем активную команду через каждые 5 команд + // If the speed is high, we display the active command every 5 commands. return setInterval(() => { - // Если у нас один лист + // If we have one sheet if (settings.commandsRange.to - settings.commandsRange.from <= commandsOnList.value) { if (status.currentCommand >= settings.commandsRange.to - settings.commandsRange.from) { status.currentCommand = 0 @@ -63,8 +63,8 @@ function commandIterator() { status.currentCommand += 5 * (readingSpeed.value * 1000) } } - // Если несколько листов - // Мы можем определить переход на следующую порцию команд при изменении status.iterationNumber + // If multiple sheets + // We can define the transition to the next instruction portion when status.iterationNumber changes else { if (currentIteration !== status.iterationNumber) { currentIteration = status.iterationNumber @@ -128,7 +128,7 @@ watch(playing, (newValue) => { } }) -// При изменении в настройках скорости чтения заново определяем интервалы +// When changing the reading speed settings, redefine the intervals watch(readingSpeed, () => { if (playing.value) { clearInterval(timerInterval)