194 lines
13 KiB
JavaScript
194 lines
13 KiB
JavaScript
// js/utils.js
|
|
import { appState } from './state.js';
|
|
import { PIXELS_PER_STEP, ZOOM_LEVELS } from './config.js';
|
|
|
|
export const DEFAULT_PROJECT_XML = `<?xml version="1.0"?>
|
|
<!DOCTYPE lmms-project>
|
|
<lmms-project version="1.0" type="song" creatorversion="1.2.2" creator="LMMS">
|
|
<head mastervol="100" masterpitch="0" timesig_denominator="4" timesig_numerator="4" bpm="140"/>
|
|
<song>
|
|
<trackcontainer visible="1" y="5" minimized="0" height="300" type="song" x="5" maximized="0" width="600">
|
|
<track muted="0" name="TripleOscillator" type="0" solo="0">
|
|
<instrumenttrack pitchrange="1" fxch="0" usemasterpitch="1" vol="100" pitch="0" pan="0" basenote="57">
|
|
<instrument name="tripleoscillator">
|
|
<tripleoscillator wavetype1="0" pan2="0" finer1="0" finel1="0" userwavefile0="" wavetype0="0" userwavefile1="" finer0="0" stphdetun1="0" vol0="33" wavetype2="0" coarse0="0" finel2="0" finer2="0" modalgo2="2" finel0="0" coarse1="-12" phoffset1="0" pan0="0" modalgo3="2" coarse2="-24" phoffset2="0" pan1="0" vol1="33" phoffset0="0" stphdetun0="0" modalgo1="2" vol2="33" stphdetun2="0" userwavefile2=""/>
|
|
</instrument>
|
|
<eldata fres="0.5" fwet="0" ftype="0" fcut="14000">
|
|
<elvol ctlenvamt="0" lspd_syncmode="0" x100="0" lpdel="0" latt="0" pdel="0" lspd="0.1" lshp="0" hold="0.5" lspd_numerator="4" userwavefile="" att="0" lamt="0" dec="0.5" lspd_denominator="4" sustain="0.5" rel="0.1" amt="0"/>
|
|
<elcut ctlenvamt="0" lspd_syncmode="0" x100="0" lpdel="0" latt="0" pdel="0" lspd="0.1" lshp="0" hold="0.5" lspd_numerator="4" userwavefile="" att="0" lamt="0" dec="0.5" lspd_denominator="4" sustain="0.5" rel="0.1" amt="0"/>
|
|
<elres ctlenvamt="0" lspd_syncmode="0" x100="0" lpdel="0" latt="0" pdel="0" lspd="0.1" lshp="0" hold="0.5" lspd_numerator="4" userwavefile="" att="0" lamt="0" dec="0.5" lspd_denominator="4" sustain="0.5" rel="0.1" amt="0"/>
|
|
</eldata>
|
|
<chordcreator chordrange="1" chord-enabled="0" chord="0"/>
|
|
<arpeggiator arpcycle="0" arptime_denominator="4" arpgate="100" arpdir="0" arpmode="0" arptime_syncmode="0" arp="0" arpmiss="0" arp-enabled="0" arptime="100" arprange="1" arpskip="0" arptime_numerator="4"/>
|
|
<midiport inputchannel="0" outputcontroller="0" fixedoutputvelocity="-1" outputchannel="1" fixedinputvelocity="-1" outputprogram="1" inputcontroller="0" readable="0" fixedoutputnote="-1" basevelocity="63" writable="0"/>
|
|
<fxchain enabled="0" numofeffects="0"/>
|
|
</instrumenttrack>
|
|
</track>
|
|
<track muted="0" name="Sample track" type="2" solo="0">
|
|
<sampletrack vol="100" pan="0">
|
|
<fxchain enabled="0" numofeffects="0"/>
|
|
</sampletrack>
|
|
</track>
|
|
<track muted="0" name="Beat/Bassline 0" type="1" solo="0">
|
|
<bbtrack>
|
|
<trackcontainer visible="0" y="5" minimized="0" height="400" type="bbtrackcontainer" x="610" maximized="0" width="700">
|
|
<track muted="0" name="Kicker" type="0" solo="0">
|
|
<instrumenttrack pitchrange="1" fxch="0" usemasterpitch="1" vol="100" pitch="0" pan="0" basenote="57">
|
|
<instrument name="kicker">
|
|
<kicker decay="440" version="1" noise="0" endfreq="40" decay_syncmode="0" decay_denominator="4" startfreq="150" env="0.163" startnote="1" dist="0.8" slope="0.06" decay_numerator="4" click="0.4" distend="0.8" gain="1" endnote="0"/>
|
|
</instrument>
|
|
<eldata fres="0.5" fwet="0" ftype="0" fcut="14000">
|
|
<elvol ctlenvamt="0" lspd_syncmode="0" x100="0" lpdel="0" latt="0" pdel="0" lspd="0.1" lshp="0" hold="0.5" lspd_numerator="4" userwavefile="" att="0" lamt="0" dec="0.5" lspd_denominator="4" sustain="0.5" rel="0.1" amt="0"/>
|
|
<elcut ctlenvamt="0" lspd_syncmode="0" x100="0" lpdel="0" latt="0" pdel="0" lspd="0.1" lshp="0" hold="0.5" lspd_numerator="4" userwavefile="" att="0" lamt="0" dec="0.5" lspd_denominator="4" sustain="0.5" rel="0.1" amt="0"/>
|
|
<elres ctlenvamt="0" lspd_syncmode="0" x100="0" lpdel="0" latt="0" pdel="0" lspd="0.1" lshp="0" hold="0.5" lspd_numerator="4" userwavefile="" att="0" lamt="0" dec="0.5" lspd_denominator="4" sustain="0.5" rel="0.1" amt="0"/>
|
|
</eldata>
|
|
<chordcreator chordrange="1" chord-enabled="0" chord="0"/>
|
|
<arpeggiator arpcycle="0" arptime_denominator="4" arpgate="100" arpdir="0" arpmode="0" arptime_syncmode="0" arp="0" arpmiss="0" arp-enabled="0" arptime="100" arprange="1" arpskip="0" arptime_numerator="4"/>
|
|
<midiport inputchannel="0" outputcontroller="0" fixedoutputvelocity="-1" outputchannel="1" fixedinputvelocity="-1" outputprogram="1" inputcontroller="0" readable="0" fixedoutputnote="-1" basevelocity="63" writable="0"/>
|
|
<fxchain enabled="0" numofeffects="0"/>
|
|
</instrumenttrack>
|
|
<pattern pos="0" muted="0" name="Kicker" steps="16" type="0"/>
|
|
</track>
|
|
</trackcontainer>
|
|
</bbtrack>
|
|
</track>
|
|
<track muted="0" name="Automation track" type="5" solo="0">
|
|
<automationtrack/>
|
|
</track>
|
|
</trackcontainer>
|
|
<track muted="0" name="Automation track" type="6" solo="0">
|
|
<automationtrack/>
|
|
<automationpattern pos="0" len="192" name="Numerator" prog="0" tens="1" mute="0"/>
|
|
<automationpattern pos="0" len="192" name="Denominator" prog="0" tens="1" mute="0"/>
|
|
<automationpattern pos="0" len="192" name="Tempo" prog="0" tens="1" mute="0"/>
|
|
<automationpattern pos="0" len="192" name="Master volume" prog="0" tens="1" mute="0"/>
|
|
<automationpattern pos="0" len="192" name="Master pitch" prog="0" tens="1" mute="0"/>
|
|
</track>
|
|
<fxmixer visible="1" y="310" minimized="0" height="333" x="5" maximized="0" width="543">
|
|
<fxchannel volume="1" muted="0" num="0" name="Master" soloed="0">
|
|
<fxchain enabled="0" numofeffects="0"/>
|
|
</fxchannel>
|
|
</fxmixer>
|
|
<ControllerRackView visible="1" y="310" minimized="0" height="200" x="680" maximized="0" width="350"/>
|
|
<pianoroll visible="0" y="5" minimized="0" height="480" x="5" maximized="0" width="860"/>
|
|
<automationeditor visible="0" y="1" minimized="0" height="400" x="1" maximized="0" width="860"/>
|
|
<projectnotes visible="0" y="10" minimized="0" height="400" x="700" maximized="0" width="679"><![CDATA[<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
|
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
|
p, li { white-space: pre-wrap; }
|
|
</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:7.5pt; font-weight:400; font-style:normal;">
|
|
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html>]]></projectnotes>
|
|
<timeline lp1pos="192" lpstate="0" lp0pos="0"/>
|
|
<controllers/>
|
|
</song>
|
|
</lmms-project>`;
|
|
|
|
/**
|
|
* Helper interna para ler o BPM do input.
|
|
* @returns {number} O BPM atual.
|
|
*/
|
|
function _getBpm() {
|
|
const bpmInput = document.getElementById("bpm-input");
|
|
return parseFloat(bpmInput.value, 10) || 120;
|
|
}
|
|
|
|
/**
|
|
* Calcula e exporta quantos compassos (beats) existem por compasso (bar).
|
|
* @returns {number} O número de batidas por compasso (ex: 4 para 4/4).
|
|
*/
|
|
export function getBeatsPerBar() {
|
|
const compassoAInput = document.getElementById("compasso-a-input");
|
|
return parseInt(compassoAInput.value, 10) || 4;
|
|
}
|
|
|
|
/**
|
|
* Calcula e exporta quantos segundos dura uma "batida" (beat).
|
|
* No contexto de BPM, uma "batida" é quase sempre uma semínima (1/4).
|
|
* @returns {number} Duração da batida em segundos.
|
|
*/
|
|
export function getSecondsPerBeat() {
|
|
return 60.0 / _getBpm();
|
|
}
|
|
|
|
/**
|
|
* Calcula e exporta quantos segundos dura um "step".
|
|
* Baseado na config, um "step" é uma semicolcheia (1/16).
|
|
* Há 4 steps (1/16) por batida (1/4).
|
|
* @returns {number} Duração do step em segundos.
|
|
*/
|
|
export function getSecondsPerStep() {
|
|
return getSecondsPerBeat() / 4.0; // 4 steps (1/16) por beat (1/4)
|
|
}
|
|
|
|
/**
|
|
* Quantiza (arredonda) um tempo em segundos para o "step" do grid mais próximo.
|
|
* @param {number} timeInSeconds - O tempo arbitrário (ex: 1.234s).
|
|
* @returns {number} O tempo alinhado ao grid (ex: 1.250s).
|
|
*/
|
|
export function quantizeTime(timeInSeconds) {
|
|
// TODO: Adicionar um toggle global (appState.global.isSnapEnabled)
|
|
|
|
const secondsPerStep = getSecondsPerStep();
|
|
if (secondsPerStep <= 0) return timeInSeconds; // Evita divisão por zero
|
|
|
|
const roundedSteps = Math.round(timeInSeconds / secondsPerStep);
|
|
return roundedSteps * secondsPerStep;
|
|
}
|
|
|
|
|
|
/**
|
|
* Calcula a quantidade de pixels que representa um segundo na timeline,
|
|
* levando em conta o BPM e o nível de zoom atual.
|
|
* @returns {number} A quantidade de pixels por segundo.
|
|
*/
|
|
export function getPixelsPerSecond() {
|
|
const bpm = _getBpm(); // Usa a helper interna
|
|
// (bpm / 60) = batidas por segundo
|
|
// * 4 = steps por segundo (assumindo 4 steps/beat)
|
|
const stepsPerSecond = (bpm / 60) * 4;
|
|
const zoomFactor = ZOOM_LEVELS[appState.global.zoomLevelIndex];
|
|
return stepsPerSecond * PIXELS_PER_STEP * zoomFactor;
|
|
}
|
|
|
|
/**
|
|
* Calcula o número total de steps no sequenciador de patterns.
|
|
* @returns {number} O número total de steps.
|
|
*/
|
|
export function getTotalSteps() {
|
|
const barsInput = document.getElementById("bars-input");
|
|
const compassoAInput = document.getElementById("compasso-a-input");
|
|
const compassoBInput = document.getElementById("compasso-b-input");
|
|
|
|
const numberOfBars = parseInt(barsInput.value, 10) || 1;
|
|
const beatsPerBar = parseInt(compassoAInput.value, 10) || 4;
|
|
const noteValue = parseInt(compassoBInput.value, 10) || 4;
|
|
const subdivisions = Math.round(16 / noteValue);
|
|
|
|
return numberOfBars * beatsPerBar * subdivisions;
|
|
}
|
|
|
|
/**
|
|
* Garante que apenas números sejam inseridos em um campo de input.
|
|
* @param {Event} event - O evento de input.
|
|
*/
|
|
export function enforceNumericInput(event) {
|
|
event.target.value = event.target.value.replace(/[^0-9]/g, "");
|
|
}
|
|
|
|
/**
|
|
* Ajusta o valor de um elemento de input com base em um passo (step),
|
|
* respeitando os limites de min/max definidos no elemento.
|
|
* @param {HTMLInputElement} inputElement - O elemento de input a ser ajustado.
|
|
* @param {number} step - O valor a ser adicionado (pode ser negativo).
|
|
*/
|
|
export function adjustValue(inputElement, step) {
|
|
let currentValue = parseInt(inputElement.value, 10) || 0;
|
|
let min = parseInt(inputElement.dataset.min, 10);
|
|
let max = parseInt(inputElement.dataset.max, 10);
|
|
let newValue = currentValue + step;
|
|
|
|
if (!isNaN(min) && newValue < min) newValue = min;
|
|
if (!isNaN(max) && newValue > max) newValue = max;
|
|
|
|
inputElement.value = newValue;
|
|
inputElement.dispatchEvent(new Event("input", { bubbles: true }));
|
|
} |