Skip to content

Commit

Permalink
Merge pull request #6 from ImSkully/dev
Browse files Browse the repository at this point in the history
v1.0.1 Release
  • Loading branch information
ImSkully authored Oct 27, 2024
2 parents d0267e9 + bbebbde commit e5a8d3e
Show file tree
Hide file tree
Showing 4 changed files with 638 additions and 72 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ A rich web based composer for creating [Ring Tone Text Transfer Language (RTTTL)
* 🎶 Dynamic RTTTL generation and browser playback using [Web Audio API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Audio_API)
* 📦 Generate or import RTTTL ringtone strings

This web application is built with barebones HTML & CSS, and vanilla JavaScript for the sake of simplicity. The frontend uses the [Tabler UI](https://github.com/tabler/tabler) framework, [BS5](https://github.com/twbs/bootstrap), and [jQuery](https://jquery.com) for UI components and interactivity.

<div align="center" style="padding: 25px;">
<h3><a href="https://imskully.github.io/rtttl-web-composer" target="_blank" title="Open RTTTL Web Composer">Open Demo Application</a></h3>
<img width="800" src=".github/readme_preview.png" alt="RTTTL Web Composer Logo" />
</div>

This web application is built with barebones HTML & CSS, and vanilla JavaScript for the sake of simplicity. The frontend uses the [Tabler UI](https://github.com/tabler/tabler) framework, [BS5](https://github.com/twbs/bootstrap), and [jQuery](https://jquery.com) for UI components and interactivity.

***

## RTTTL
Expand Down
124 changes: 63 additions & 61 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
<link rel="shortcut icon" href="images/icon.png" />

<!-- [CSS] -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/[email protected]/dist/css/tabler.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/[email protected]/dist/css/tabler.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/notyf/3.10.0/notyf.min.css" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link rel="stylesheet" href="css/style.css" />

<title>RTTTL Web Composer</title>
Expand All @@ -43,14 +44,20 @@
<ul class="navbar-nav">
<!-- Home -->
<li class="nav-item active">
<a class="nav-link" href="#">
<span class="nav-link-icon d-md-none d-lg-inline-block"><i class="ti ti-music"></i></span>
<a class="nav-link" href="index.html">
<span class="nav-link-icon d-md-none d-lg-inline-block"><i class="icon ti ti-music"></i></span>
<span class="nav-link-title">RTTTL Composer</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="rtttl_specification.html">
<span class="nav-link-icon d-md-none d-lg-inline-block"><i class="icon ti ti-question-mark"></i></span>
<span class="nav-link-title">What is RTTTL?</span>
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://github.com/ImSkully/rtttl-web-composer" target="_blank">
<span class="nav-link-icon d-md-none d-lg-inline-block"><i class="ti ti-brand-github"></i></span>
<span class="nav-link-icon d-md-none d-lg-inline-block"><i class="icon ti ti-brand-github"></i></span>
<span class="nav-link-title">Source</span>
</a>
</li>
Expand All @@ -59,10 +66,10 @@
<div class="d-md-flex">
<!-- Theme Switch Buttons -->
<a href="?theme=dark" class="nav-link px-0 hide-theme-dark" title="Enable Dark Mode" data-bs-toggle="tooltip" data-bs-placement="bottom">
<i class="ti ti-sun"></i>
<i class="icon ti ti-sun"></i>
</a>
<a href="?theme=light" class="nav-link px-0 hide-theme-light" title="Enable Light Mode" data-bs-toggle="tooltip" data-bs-placement="bottom">
<i class="ti ti-moon"></i>
<i class="icon ti ti-moon"></i>
</a>
</div>
</div>
Expand Down Expand Up @@ -118,10 +125,30 @@
<th>C</th>
</tr>
<tr>
<th>d</th>
<th>
<span
class="form-help mx-1"
data-bs-toggle="popover"
data-bs-placement="top"
data-bs-html="true"
data-bs-content="Controls the duration for individual notes, any column with <code>d</code> will use the default <strong>Duration</strong> that is set below."
>?</span
>
d
</th>
</tr>
<tr>
<th>o</th>
<th>
<span
class="form-help mx-1"
data-bs-toggle="popover"
data-bs-placement="top"
data-bs-html="true"
data-bs-content="Adjust the octave (pitch) for individual notes, any column with <code>o</code> will use the default <strong>Octave</strong> that is set below."
>?</span
>
o
</th>
</tr>
</tbody>
</table>
Expand All @@ -146,7 +173,16 @@
<input type="range" class="form-range mt-2" value="63" min="5" max="900" id="bpm-slider" />
</div>
<div class="col-3">
<input type="text" class="form-control disabled" id="bpm-input" value="63" readonly />
<input
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Use left slider to adjust"
type="text"
class="form-control disabled"
id="bpm-input"
value="63"
readonly
/>
</div>
</div>
</div>
Expand Down Expand Up @@ -182,7 +218,7 @@
<div class="my-3">
<label class="form-label" for="rtttl-output-textbox">RTTTL Output</label>
<div class="input-icon mb-3">
<span class="input-icon-addon"><i class="ti ti-music"></i></span>
<span class="input-icon-addon"><i class="icon ti ti-music"></i></span>
<input type="text" class="form-control font-terminal" id="rtttl-output-textbox" placeholder="song_name:d=4,o=5,b=140:..." />
</div>
</div>
Expand All @@ -196,66 +232,31 @@
<!-- Play Button -->
<input type="radio" class="btn-check" name="btn-radio-toolbar" id="radio-toolbar-play" autocomplete="off" />
<label for="radio-toolbar-play" class="btn btn-success">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler icons-tabler-outline icon-tabler-player-play"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M7 4v16l13 -8z" />
</svg>
<i class="icon ti ti-player-play"></i>
Play
</label>
<!-- Stop Button -->
<input type="radio" class="btn-check" name="btn-radio-toolbar" id="radio-toolbar-stop" autocomplete="off" disabled="disabled" />
<label for="radio-toolbar-stop" class="btn btn-danger">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler icons-tabler-outline icon-tabler-player-stop"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M5 5m0 2a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2 -2z" />
</svg>
<i class="icon ti ti-player-stop"></i>
Stop
</label>

<!-- Load From Text Area Button -->
<input type="radio" class="btn-check" name="btn-radio-toolbar" id="radio-toolbar-load-from-textarea" autocomplete="off" />
<label for="radio-toolbar-load-from-textarea" class="btn btn-info">
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler icons-tabler-outline icon-tabler-book-upload"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M14 20h-8a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12v5" />
<path d="M11 16h-5a2 2 0 0 0 -2 2" />
<path d="M15 16l3 -3l3 3" />
<path d="M18 13v9" />
</svg>
<i class="icon ti ti-book-upload"></i>
Load From Text Area
</label>

<!-- Volume Button -->
<button class="btn btn-icon bg-azure-lt" name="btn-radio-toolbar" data-bs-toggle="dropdown" autocomplete="off">
<i class="icon ti ti-volume"></i>
<div class="dropdown-menu p-2">
<label class="form-label text-center" id="radio-toolbar-slider-volume-text" for="radio-toolbar-slider-volume">20%</label>
<input type="range" class="form-range" value="20" min="0" max="100" id="radio-toolbar-slider-volume" />
</div>
</button>
</div>
</div>
</div>
Expand Down Expand Up @@ -294,8 +295,9 @@
</div>

<!-- Scripts -->
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@tabler/[email protected]/dist/js/tabler.min.js"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdn.jsdelivr.net/npm/@tabler/[email protected]/dist/js/tabler.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/notyf/3.10.0/notyf.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="js/main.js"></script>
<script src="js/theme.js"></script>
</body>
Expand Down
43 changes: 34 additions & 9 deletions src/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,19 @@ const ELEMENTS = {
BPM_SLIDER: $("#bpm-slider"),
DURATION_SELECTOR: $("#duration-selector"),
BPM_INPUT: $("#bpm-input"),
VOLUME_TEXT: $("#radio-toolbar-slider-volume-text"),
BUTTON: {
PLAY: $("#radio-toolbar-play"),
STOP: $("#radio-toolbar-stop"),
LOAD_FROM_TEXTAREA: $("#radio-toolbar-load-from-textarea")
LOAD_FROM_TEXTAREA: $("#radio-toolbar-load-from-textarea"),
VOLUME: $("#radio-toolbar-slider-volume")
}
};

/** Notyf.js instance. */
// eslint-disable-next-line no-undef
const NOTIFY = new Notyf({ position: { x: "center", y: "top" }, duration: 5000 });

/** Global variables and static definitions. */
const GLOBALS = {
/** The number of note columns to create within the composer table. */
Expand Down Expand Up @@ -75,7 +81,7 @@ const COMPOSED_NOTES = [];
const RTTTL = {
/** The AudioPlayer handles RTTTL playback via Web Audio API. */
AudioPlayer: (() => {
let context, noteVolume, oscillator, playbackTimeout;
let context, noteVolume, oscillator, gainNode, playbackTimeout;
let highlighterTimeouts = [];
let isPlaying = false;
let elapsedTime = 0;
Expand All @@ -91,8 +97,8 @@ const RTTTL = {
context = new (window.AudioContext || window.webkitAudioContext)();

// Create a gain node for volume control and connect it to the destination.
const gainNode = context.createGain();
gainNode.gain.value = 0.1; // TODO: Make this adjustable on the frontend.
gainNode = context.createGain();
gainNode.gain.value = 0.20;
gainNode.connect(context.destination);

// Create the oscillator and connect it to the gain node.
Expand All @@ -105,6 +111,15 @@ const RTTTL = {
oscillator.connect(noteVolume);
};

/**
* Sets the playback volume level.
* @param {number} volume The volume level to set.
*/
const setVolume = (volume) => {
if (!context) init(); // Initialize the audio context if not already done.
gainNode.gain.value = volume / 100;
};

/**
* Starts audio playback of the RTTTL tune.
*/
Expand Down Expand Up @@ -188,7 +203,7 @@ const RTTTL = {
ELEMENTS.BUTTON.LOAD_FROM_TEXTAREA.attr("disabled", false);
};

return { start, stop, isPlaying: () => isPlaying };
return { start, stop, setVolume, isPlaying: () => isPlaying };
})(),

/** The RTTTL player settings and state. */
Expand Down Expand Up @@ -322,7 +337,7 @@ function loadFromTextArea() {
console.debug(`@loadFromTextArea(): Loading RTTTL string '${loadedNoteText}'..`);

const RTTTLSections = loadedNoteText.split(":");
if (RTTTLSections.length < 3) return console.error(`Invalid RTTTL string, expected 3 sections but got ${RTTTLSections.length}.`);
if (RTTTLSections.length < 3) return NOTIFY.error(`Invalid RTTTL string, expected 3 sections but only found ${RTTTLSections.length}.`);

/**
* [Section 1]
Expand All @@ -346,17 +361,17 @@ function loadFromTextArea() {

// Validate that all required settings are present.
if (!toneSettings.d || !GLOBALS.DURATION_VALUES.includes(toneSettings.d)) {
console.warn(`@loadFromTextArea(): Missing or invalid default duration (d=${toneSettings.d}) setting, using default.`);
NOTIFY.error(`Missing or invalid default duration setting (d=${toneSettings.d}), using default.`);
toneSettings.d = GLOBALS.DEFAULT.DURATION;
}

if (!toneSettings?.o || !GLOBALS.OCTAVE_VALUES.includes(toneSettings.o)) {
console.warn(`@loadFromTextArea(): Missing or invalid default octave (o=${toneSettings.o}) setting, using default.`);
NOTIFY.error(`Missing or invalid default octave setting (o=${toneSettings.o}), using default.`);
toneSettings.o = GLOBALS.DEFAULT.OCTAVE;
}

if (!toneSettings?.b) {
console.warn(`@loadFromTextArea(): Missing default BPM (b=${toneSettings.b}) setting, using default.`);
NOTIFY.error(`Missing default BPM (b=${toneSettings.b}) setting, using default.`);
toneSettings.b = GLOBALS.DEFAULT.BPM;
}

Expand All @@ -379,6 +394,9 @@ function loadFromTextArea() {
*/
const notesData = RTTTLSections[2].toLowerCase().replace(/\s/g, "");

// Validate that note data is present.
if (notesData.length === 0) return NOTIFY.error("No note data was found in the RTTTL string to load!");

notesData.split(/[,;]/).forEach((tone, i) => {
let match = tone
.replace(/\./g, "") // Remove dot notations (not currently supported).
Expand Down Expand Up @@ -518,6 +536,13 @@ $(() => {
ELEMENTS.BUTTON.STOP.on("click", RTTTL.AudioPlayer.stop); // Stop playback.
ELEMENTS.BUTTON.LOAD_FROM_TEXTAREA.on("click", loadFromTextArea); // Load RTTTL from text area.

// Volume slider.
ELEMENTS.BUTTON.VOLUME.on("input", () => {
const newVolume = +ELEMENTS.BUTTON.VOLUME.val();
RTTTL.AudioPlayer.setVolume(newVolume);
ELEMENTS.VOLUME_TEXT.text(`${newVolume}%`);
});

/*========================================================================
# Keybind Event Listeners #
========================================================================*/
Expand Down
Loading

0 comments on commit e5a8d3e

Please sign in to comment.