Compare commits

..

1 Commits

Author SHA1 Message Date
Kevin Thomas
3ea03c92db Add warning message 2022-04-17 16:12:35 -07:00
47 changed files with 8819 additions and 20270 deletions

View File

@@ -1,2 +0,0 @@
CHOKIDAR_USEPOLLING=true
WDS_SOCKET_PORT=0

View File

@@ -1,39 +0,0 @@
{
"name": "Noisedash Dev",
"image": "mcr.microsoft.com/devcontainers/node:20-bookworm",
"forwardPorts": [8080, 1432],
"portsAttributes": {
"8080": { "label": "Vue dev server" },
"1432": { "label": "Noisedash API" }
},
"postCreateCommand": "npm install",
"remoteEnv": {
"HOST": "0.0.0.0"
},
"containerEnv": {
"HOST": "0.0.0.0"
},
"runArgs": ["--env-file", ".devcontainer/devcontainer.env"],
"remoteUser": "node",
"customizations": {
"vscode": {
"settings": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"eslint.format.enable": true,
"eslint.validate": ["javascript", "vue"],
"terminal.integrated.defaultProfile.linux": "bash",
"vetur.validation.template": true,
"vetur.validation.script": true,
"vetur.validation.style": true
},
"extensions": [
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"octref.vetur"
]
}
},
"updateContentCommand": "",
"onCreateCommand": "mkdir -p samples sessions log",
"postStartCommand": "npm run server & npm run serve"
}

View File

@@ -8,7 +8,7 @@ module.exports = {
'@vue/standard'
],
parserOptions: {
parser: '@babel/eslint-parser'
parser: 'babel-eslint'
},
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',

3
.gitattributes vendored
View File

@@ -1,3 +0,0 @@
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf

View File

@@ -1,17 +0,0 @@
Gitea Actions for Docker builds
This folder contains workflows for building and pushing Docker images to the Gitea Container Registry.
Setup
- Ensure Gitea Actions is enabled in your instance and for this repo.
- Create the following repository secrets:
- REGISTRY_HOST: your Gitea host (e.g., gitea.example.com)
- REGISTRY_OWNER: your namespace/user/org (e.g., ryan)
- REGISTRY_REPO: repository name (e.g., noisedash)
- REGISTRY_USERNAME: registry username
- REGISTRY_PASSWORD: registry password or a scoped token
Notes
- The workflow builds on pushes to main/master/dev and on tags v*.*.*.
- It produces multi-arch images (amd64, arm64). Adjust platforms if not needed.
- Image name resolves to: ${REGISTRY_HOST}/${REGISTRY_OWNER}/${REGISTRY_REPO} with tags derived from branch/tag/sha.

View File

@@ -1,69 +0,0 @@
name: build-and-push-docker
on:
push:
branches: [ main, master, dev ]
tags:
- 'v*.*.*'
pull_request:
branches: [ main, master, dev ]
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install system Dependencies
run: |
apt-get update && apt-get install -y curl jq docker.io
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Gitea Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY_HOST }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY_HOST }}/${{ env.REGISTRY_OWNER }}/${{ env.REGISTRY_REPO }}
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha
- name: Build (PR only)
if: ${{ github.event_name == 'pull_request' }}
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: false
platforms: linux/amd64
labels: ${{ steps.meta.outputs.labels }}
- name: Build and push (branches/tags)
if: ${{ github.event_name != 'pull_request' }}
uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
env:
# Example: registry.example.com or gitea.example.com
REGISTRY_HOST: ${{ secrets.REGISTRY_HOST }}
# Example: ryan (your user or org in Gitea)
REGISTRY_OWNER: ${{ secrets.REGISTRY_OWNER }}
# Example: noisedash (this repo name)
REGISTRY_REPO: ${{ secrets.REGISTRY_REPO }}

1
.github/FUNDING.yml vendored
View File

@@ -1,4 +1,3 @@
# These are supported funding model platforms
github: kaythomas0
custom: "https://kaythomas.dev/cryptocurrency.html"

View File

@@ -1,8 +0,0 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
# Disable version updates for npm dependencies
open-pull-requests-limit: 0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,38 +0,0 @@
name: Docker Image CI
on:
pull_request:
types:
- labeled
branches:
- 'main'
jobs:
buildx:
if: ${{ github.event.label.name == 'run-workflow' }}
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Set up QEMU
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v1
-
name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
-
name: Build and push
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64,linux/arm/v7,linux/arm64
push: true
tags: noisedash/noisedash:latest,noisedash/noisedash:${{ github.head_ref }}

View File

@@ -1,6 +1,4 @@
## Project setup
Requires [Node](https://nodejs.org/en/download/) and [Vue CLI](https://cli.vuejs.org/guide/installation.html)
```
npm install
```
@@ -25,22 +23,5 @@ npm run build
npm run lint
```
### Directory Summary
Here are some of the more important files and directories:
* `config/default.json`: Contains the default configuration file
* `server/*`: Where all of the node server related code is
* `server/app.js`: The main server file where server settings are set
* `server/db.js`: Where the database is created
* `server/logger.js`: Where the logger is created and configured
* `server/bin/www.js`: The entry point of the server application (what you run to start the server)
* `server/boot/*`: These are run on server startup
* `server/routes/*`: Where all of the server routes and logic are defined
* `src/*`: Contains all the frontend code
* `src/components/*`: Where all of the Vue components are defined, split into vue and js files for each component
* `src/router/index.js`: Where all the routing and route-protection logic is defined
* `src/views/*`: Contains all the views
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

@@ -1,8 +1,8 @@
FROM node:20
FROM node:14
LABEL maintainer="kaythomas@pm.me"
WORKDIR /var/noisedash
COPY package*.json ./
RUN npm install --force
RUN npm install
COPY . .
ENV NODE_ENV production
RUN npm run build

View File

@@ -2,16 +2,16 @@
Self-hostable web tool for generating ambient noises
![Noisedash](https://raw.githubusercontent.com/kaythomas0/noisedash/dev/.github/noisedash-screenshot-1.jpg)
![Noisedash](./.github/noisedash-screenshot-1.jpg)
(More screenshots on the [wiki](https://github.com/kaythomas0/noisedash/wiki/Screenshots))
# Features
* Generate and customize ambient noises and user-uploadable samples (leveraging [Tone.js](https://github.com/Tonejs/Tone.js/))
* Save "noise profiles" so you can easily switch between your created soundscapes. Import and export them for easy sharing, record them for use elsewhere
* Save "noise profiles" so you can easily switch between your created soundscapes
* Fine-tune your noises with audio processing tools like filters, LFOs, and effects
* Upload and edit audio samples (e.g rain, wind, thunder) to combine with your generated noises. Add effects to them and set playback modes
* Upload audio samples (e.g rain, wind, thunder) to combine with your generated noises
* Use admin tools to manage multiple users
* Mobile friendly
@@ -23,9 +23,6 @@ Requires docker and docker-compose
* Download the provided [docker-compose.yml file](https://github.com/kaythomas0/noisedash/blob/main/docker-compose.yml)
* In the same directory as the docker-compose file, created a folder called `config`, and inside it, put the provided [config file](https://github.com/kaythomas0/noisedash/blob/main/config/default.json)
* `maxSampleSize` is in bytes - 10GB by default
* Keep `tls` as `false` if using an external web server like nginx
* `production.json` exists in the source code and is left empty intentionally for the reason outlined here: https://github.com/node-config/node-config/wiki/Strict-Mode#node_env-value-of-node_env-did-not-match-any-deployment-config-file-names=
* Edit the config file to your preference
* Bring the container up:
@@ -37,26 +34,9 @@ docker-compose up -d
(Raspberry Pi compatible images are available, see armv7 images on [Docker Hub](https://hub.docker.com/repository/docker/noisedash/noisedash))
## Kubernetes
You can apply the manifest.yaml in the kubernetes folder to install Noisedash into your Kubernetes cluster.
Optionally, uncomment the last lines in the file to also create an ingress. The ingress, commented out by default, needs to have the clusterIssuser annotation set to your cluster issuer (default: letsencrypt-prod) and the ingress class set to your Ingress class (default: Nginx)
``` bash
$ kubectl apply -f ./kubernetes/manifest.yaml
persistentvolumeclaim/db-pvc created
persistentvolumeclaim/samples-pvc created
deployment.apps/noisedash created
service/noisedash created
configmap/noisedashcfg created
ingress.networking.k8s.io/noisedashingress created
```
## From Source
Requires node 20 and npm
Requires node 14 and npm
* Clone the repo:
@@ -85,21 +65,3 @@ npm run server-prod
# Contributing
See [CONTRIBUTING.md](https://github.com/kaythomas0/noisedash/blob/main/CONTRIBUTING.md)
# License
Noisedash, a self-hostable web tool for generating ambient noises
Copyright (C) 2021 Kay Thomas <kaythomas@pm.me>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.

View File

@@ -3,9 +3,9 @@
"listeningPort": 1432,
"sessionFileStorePath": "sessions",
"sampleUploadPath": "samples",
"maxSampleSize": 10737418240,
"maxSampleSize": 10737418240, // In bytes, 10GB by default
"logFile": "log/noisedash.log",
"tls": false,
"tls": false, // Keep this as false if using an external web server like nginx
"tlsKey": "certs/key.pem",
"tlsCert": "certs/cert.pem"
}

View File

@@ -1 +1 @@
{}
{} // Left empty intentionally: https://github.com/node-config/node-config/wiki/Strict-Mode#node_env-value-of-node_env-did-not-match-any-deployment-config-file-names=

View File

@@ -1,240 +0,0 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: samples-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: noisedash
spec:
replicas: 1
selector:
matchLabels:
app: noisedash
template:
metadata:
labels:
app: noisedash
spec:
containers:
- name: noisedash
image: noisedash/noisedash:latest
ports:
- containerPort: 1432
volumeMounts:
- name: db
mountPath: /var/noisedash/db
- name: samples
mountPath: /var/noisedash/samples
- name: config
mountPath: /var/noisedash/config/default.json
subPath: config.json
volumes:
- name: db
persistentVolumeClaim:
claimName: db-pvc
- name: samples
persistentVolumeClaim:
claimName: samples-pvc
- name: config
configMap:
name: noisedashcfg
---
apiVersion: v1
kind: Service
metadata:
name: noisedash
spec:
selector:
app: noisedash
ports:
- protocol: TCP
port: 80
targetPort: 1432
---
apiVersion: v1
kind: ConfigMap
metadata:
name: noisedashcfg
data:
config.json: |
{
"Server": {
"listeningPort": 1432,
"sessionFileStorePath": "sessions",
"sampleUploadPath": "samples",
"maxSampleSize": 10737418240,
"logFile": "log/noisedash.log",
"tls": false,
"tlsKey": "certs/key.pem",
"tlsCert": "certs/cert.pem"
}
}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
labels:
app.kubernetes.io/instance: noisedash
name: noisedashingress
spec:
rules:
- host: noisedash.freshbrewed.science
http:
paths:
- backend:
service:
name: noisedash
port:
number: 80
path: /
pathType: ImplementationSpecific
tls:
- hosts:
- noisedash.freshbrewed.science
secretName: noisedash-tls
builder@DESKTOP-QADGF36:~/Workspaces/pyplanereport$
builder@DESKTOP-QADGF36:~/Workspaces/pyplanereport$ cat noiseall.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: samples-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: noisedash
spec:
replicas: 1
selector:
matchLabels:
app: noisedash
template:
metadata:
labels:
app: noisedash
spec:
containers:
- name: noisedash
image: noisedash/noisedash:latest
ports:
- containerPort: 1432
volumeMounts:
- name: db
mountPath: /var/noisedash/db
- name: samples
mountPath: /var/noisedash/samples
- name: config
mountPath: /var/noisedash/config/default.json
subPath: config.json
volumes:
- name: db
persistentVolumeClaim:
claimName: db-pvc
- name: samples
persistentVolumeClaim:
claimName: samples-pvc
- name: config
configMap:
name: noisedashcfg
---
apiVersion: v1
kind: Service
metadata:
name: noisedash
spec:
selector:
app: noisedash
ports:
- protocol: TCP
port: 80
targetPort: 1432
---
apiVersion: v1
kind: ConfigMap
metadata:
name: noisedashcfg
data:
config.json: |
{
"Server": {
"listeningPort": 1432,
"sessionFileStorePath": "sessions",
"sampleUploadPath": "samples",
"maxSampleSize": 10737418240,
"logFile": "log/noisedash.log",
"tls": false,
"tlsKey": "certs/key.pem",
"tlsCert": "certs/cert.pem"
}
}
# ---
# apiVersion: networking.k8s.io/v1
# kind: Ingress
# metadata:
# annotations:
# cert-manager.io/cluster-issuer: letsencrypt-prod
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
# nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
# nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
# labels:
# app.kubernetes.io/instance: noisedash
# name: noisedashingress
# spec:
# rules:
# - host: noisedash.freshbrewed.science
# http:
# paths:
# - backend:
# service:
# name: noisedash
# port:
# number: 80
# path: /
# pathType: ImplementationSpecific
# tls:
# - hosts:
# - noisedash.freshbrewed.science
# secretName: noisedash-tls

27398
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,59 +1,57 @@
{
"name": "noisedash",
"version": "0.7.0",
"private": true,
"descriptions": "Self-hostable web tool for generating ambient noises",
"homepage": "https://github.com/kaythomas0/noisedash",
"bugs": "https://github.com/kaythomas0/noisedash/issues",
"license": "AGPL-3.0-or-later",
"author": "Kay Thomas <kaythomas@pm.me> (https://kaythomas.dev)",
"version": "0.2.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"server": "node server/bin/www.js",
"server-prod": "NODE_ENV=production node server/bin/www.js"
"server-prod": "NODE_ENV=production node server/bin/www.js",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@vscode/sqlite3": "^5.0.8",
"axios": "^1.6.0",
"axios": "^0.21.4",
"config": "^3.3.6",
"connect-history-api-fallback": "^1.6.0",
"cookie-parser": "^1.4.5",
"core-js": "^3.23.5",
"express": "^4.18.1",
"express-session": "^1.17.3",
"multer": "^1.4.5-lts.1",
"passport": "^0.6.0",
"core-js": "^3.19.1",
"express": "^4.17.1",
"express-session": "^1.17.2",
"multer": "^1.4.3",
"passport": "^0.4.1",
"passport-local": "^1.0.0",
"path": "^0.12.7",
"session-file-store": "^1.5.0",
"tone": "^14.7.77",
"vue": "^2.7.16",
"vue-router": "^3.5.4",
"vuetify": "^2.6.10",
"vue": "^2.6.11",
"vue-router": "^3.5.3",
"vuetify": "^2.5.12",
"winston": "^3.3.3"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-pwa": "^5.0.8",
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-plugin-router": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"@vue/eslint-config-standard": "^6.1.0",
"eslint": "^7.32.0",
"@vue/cli-plugin-babel": "^4.5.15",
"@vue/cli-plugin-eslint": "^4.5.15",
"@vue/cli-plugin-router": "^4.5.15",
"@vue/cli-service": "^4.5.15",
"@vue/eslint-config-standard": "^5.1.2",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-html": "^6.2.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-import": "^2.25.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^7.20.0",
"eslint-plugin-vue": "^6.2.2",
"sass": "~1.32.0",
"sass-loader": "^10.0.0",
"vue-cli-plugin-vuetify": "^2.5.8",
"vue-template-compiler": "^2.7.16",
"vue-cli-plugin-vuetify": "^2.4.3",
"vue-template-compiler": "^2.6.11",
"vuetify-loader": "^1.7.3"
},
"bugs": "https://github.com/kaythomas0/noisedash/issues",
"descriptions": "Self-hostable web tool for generating ambient noises",
"homepage": "https://github.com/kaythomas0/noisedash",
"license": "AGPL-3.0-or-later"
}
}

View File

@@ -5,8 +5,6 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<meta name="theme-color" content="#121212">
<link rel="manifest" href="<%= BASE_URL %>manifest.webmanifest">
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">

View File

@@ -1,16 +0,0 @@
{
"name": "Noisedash",
"short_name": "Noisedash",
"start_url": "/",
"display": "standalone",
"background_color": "#121212",
"theme_color": "#121212",
"description": "Ambient noise generator",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
]
}

View File

@@ -36,8 +36,7 @@ app.use(session({
store: new FileStore(fileStoreOptions),
secret: sessionSecret,
resave: true,
saveUninitialized: true,
cookie: { sameSite: 'strict' }
saveUninitialized: true
}))
app.use((req, res, next) => {
const msgs = req.session.messages || []

View File

@@ -68,4 +68,5 @@ function onListening () {
? 'pipe ' + addr
: 'port ' + addr.port
logger.log('info', 'Listening on %s', bind)
logger.log('info', 'WARNING, IMPORTANT: It looks like you are using the kevinthomas0/noisedash docker image. Please update your image to noisedash/noisedash. The kevinthomas0/noisedash image will no longer receive updates.')
}

View File

@@ -59,46 +59,14 @@ module.exports = function () {
} else {
const userVersion = row.user_version
db.serialize(() => {
if (userVersion < 1) {
db.run('ALTER TABLE samples ADD COLUMN fade_in REAL DEFAULT 0')
db.run('ALTER TABLE samples ADD COLUMN loop_points_enabled INTEGER DEFAULT 0')
db.run('ALTER TABLE samples ADD COLUMN loop_start REAL DEFAULT 0')
db.run('ALTER TABLE samples ADD COLUMN loop_end REAL DEFAULT 0')
if (userVersion === 0) {
db.run('ALTER TABLE samples ADD COLUMN fade_in REAL DEFAULT 0')
db.run('ALTER TABLE samples ADD COLUMN loop_points_enabled INTEGER DEFAULT 0')
db.run('ALTER TABLE samples ADD COLUMN loop_start REAL DEFAULT 0')
db.run('ALTER TABLE samples ADD COLUMN loop_end REAL DEFAULT 0')
db.run('PRAGMA user_version = 1')
}
if (userVersion < 2) {
db.run('ALTER TABLE users ADD COLUMN preferences TEXT DEFAULT "{}"')
db.run('PRAGMA user_version = 2')
}
if (userVersion < 3) {
db.run('ALTER TABLE profiles_samples ADD COLUMN reverb_enabled INTEGER DEFAULT 0')
db.run('ALTER TABLE profiles_samples ADD COLUMN reverb_pre_delay REAL DEFAULT 0')
db.run('ALTER TABLE profiles_samples ADD COLUMN reverb_decay REAL DEFAULT 0')
db.run('ALTER TABLE profiles_samples ADD COLUMN reverb_wet INTEGER DEFAULT 0')
db.run('ALTER TABLE profiles_samples ADD COLUMN playback_mode TEXT DEFAULT "continuous"')
db.run('ALTER TABLE profiles_samples ADD COLUMN sporadic_min INTEGER DEFAULT 30')
db.run('ALTER TABLE profiles_samples ADD COLUMN sporadic_max INTEGER DEFAULT 300')
db.run('PRAGMA user_version = 3')
}
if (userVersion < 4) {
db.run('UPDATE users SET preferences = ? WHERE preferences = ?',
['{"accentColor":{"alpha":1,"hex":"#607D8B","hexa":"#607D8BFF","hsla":{"h":200,"s":18,"l":46,"a":1},"hsva":{"h":200,"s":31,"v":55,"a":1},"hue":200,"rgba":{"r":96,"g":125,"b":139,"a":1}}}', '{}'],
(err) => {
if (err) {
logger.error(err)
} else {
db.run('PRAGMA user_version = 4')
}
})
}
})
db.run('PRAGMA user_version = 1')
}
}
})
})

View File

@@ -36,14 +36,8 @@ router.get('/admin', (req, res) => {
})
router.get('/logout', (req, res) => {
req.logout((err) => {
if (err) {
logger.error(err)
res.sendStatus(500)
} else {
res.sendStatus(200)
}
})
req.logout()
res.sendStatus(200)
})
router.get('/setup', (req, res) => {

View File

@@ -59,28 +59,10 @@ router.post('/profiles', (req, res) => {
profileID = this.lastID
req.body.samples.forEach(s => {
db.run(`INSERT INTO profiles_samples(
profile,
sample,
volume,
reverb_enabled,
reverb_pre_delay,
reverb_decay,
reverb_wet,
playback_mode,
sporadic_min,
sporadic_max)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
db.run('INSERT INTO profiles_samples (profile, sample, volume) VALUES (?, ?, ?)', [
profileID,
s.id,
s.volume,
s.reverbEnabled,
s.reverbPreDelay,
s.reverbDecay,
s.reverbWet,
s.playbackMode,
s.sporadicMin,
s.sporadicMax
s.volume
],
(err) => {
if (err) {
@@ -222,28 +204,10 @@ router.put('/profiles/:profileId', (req, res) => {
})
req.body.samples.forEach(s => {
db.run(`INSERT INTO profiles_samples(
profile,
sample,
volume,
reverb_enabled,
reverb_pre_delay,
reverb_decay,
reverb_wet,
playback_mode,
sporadic_min,
sporadic_max)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
db.run('INSERT INTO profiles_samples (profile, sample, volume) VALUES (?, ?, ?)', [
req.params.profileId,
s.id,
s.volume,
s.reverbEnabled,
s.reverbPreDelay,
s.reverbDecay,
s.reverbWet,
s.playbackMode,
s.sporadicMin,
s.sporadicMax
s.volume
],
(err) => {
if (err) {
@@ -391,13 +355,6 @@ router.get('/profiles/:profileId', (req, res) => {
samples.id,
name,
profiles_samples.volume,
profiles_samples.reverb_enabled as reverbEnabled,
profiles_samples.reverb_pre_delay as reverbPreDelay,
profiles_samples.reverb_decay as reverbDecay,
profiles_samples.reverb_wet as reverbWet,
profiles_samples.playback_mode as playbackMode,
profiles_samples.sporadic_min as sporadicMin,
profiles_samples.sporadic_max as sporadicMax,
fade_in as fadeIn,
loop_points_enabled as loopPointsEnabled,
loop_start as loopStart,
@@ -425,13 +382,6 @@ router.get('/profiles/:profileId', (req, res) => {
sample.loopPointsEnabled = row.loopPointsEnabled === 1
sample.loopStart = row.loopStart
sample.loopEnd = row.loopEnd
sample.reverbEnabled = row.reverbEnabled === 1
sample.reverbPreDelay = row.reverbPreDelay
sample.reverbDecay = row.reverbDecay
sample.reverbWet = row.reverbWet
sample.playbackMode = row.playbackMode
sample.sporadicMin = row.sporadicMin
sample.sporadicMax = row.sporadicMax
samples.push(sample)
})

View File

@@ -9,11 +9,7 @@ router.get('/users/current', (req, res) => {
return res.sendStatus(401)
}
db.get(`SELECT
is_admin as isAdmin,
dark_mode as darkMode,
can_upload as canUpload,
* FROM users WHERE id = ?`, [req.user.id], (err, row) => {
db.get('SELECT is_admin as isAdmin, dark_mode as darkMode, can_upload as canUpload, * FROM users WHERE id = ?', [req.user.id], (err, row) => {
if (err) {
logger.error(err)
return res.sendStatus(500)
@@ -28,7 +24,6 @@ router.get('/users/current', (req, res) => {
user.isAdmin = row.isAdmin === 1
user.darkMode = row.darkMode === 1
user.canUpload = row.canUpload === 1
user.preferences = JSON.parse(row.preferences)
}
res.json({ user: user })
@@ -72,8 +67,6 @@ router.post('/users', (req, res) => {
return res.sendStatus(500)
}
const defaultPreferences = '{"accentColor":{"alpha":1,"hex":"#607D8B","hexa":"#607D8BFF","hsla":{"h":200,"s":18,"l":46,"a":1},"hsva":{"h":200,"s":31,"v":55,"a":1},"hue":200,"rgba":{"r":96,"g":125,"b":139,"a":1}}}'
if (row.count !== 0) {
if (!req.user) {
return res.sendStatus(401)
@@ -96,16 +89,15 @@ router.post('/users', (req, res) => {
return res.sendStatus(500)
}
db.run(`INSERT INTO users (username, hashed_password, salt, name, is_admin, dark_mode, can_upload, preferences)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
db.run(`INSERT INTO users (username, hashed_password, salt, name, is_admin, dark_mode, can_upload)
VALUES (?, ?, ?, ?, ?, ?, ?)`, [
req.body.username,
hashedPassword,
salt,
req.body.name,
req.body.isAdmin,
req.body.darkMode,
req.body.canUpload,
defaultPreferences
req.body.canUpload
], (err) => {
if (err) {
logger.error(err)
@@ -128,16 +120,15 @@ router.post('/users', (req, res) => {
return res.sendStatus(500)
}
db.run(`INSERT INTO users (username, hashed_password, salt, name, is_admin, dark_mode, can_upload, preferences)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [
db.run(`INSERT INTO users (username, hashed_password, salt, name, is_admin, dark_mode, can_upload)
VALUES (?, ?, ?, ?, ?, ?, ?)`, [
req.body.username,
hashedPassword,
salt,
req.body.name,
req.body.isAdmin,
req.body.darkMode,
req.body.canUpload,
defaultPreferences
req.body.canUpload
], function (err) {
if (err) {
logger.error(err)
@@ -296,23 +287,4 @@ router.delete('/users/:userId', (req, res) => {
})
})
router.patch('/users/preferences', (req, res) => {
if (!req.user) {
return res.sendStatus(401)
}
const preferences = JSON.stringify(req.body.preferences)
db.serialize(() => {
db.run('UPDATE users SET preferences = ? WHERE id = ?', [preferences, req.user.id], (err) => {
if (err) {
logger.error(err)
return res.sendStatus(500)
} else {
return res.sendStatus(200)
}
})
})
})
module.exports = router

View File

@@ -8,10 +8,6 @@
</v-col>
</v-row>
<h2 class="headline font-weight-bold mb-3">
User Management
</h2>
<v-col cols="12">
<v-row>
ID: {{ currentUser.id }}
@@ -28,9 +24,9 @@
v-model="changePasswordDialog"
max-width="600px"
>
<template #activator="{ on, attrs }">
<template v-slot:activator="{ on, attrs }">
<v-btn
class="mt-5 mb-10"
class="my-3"
v-bind="attrs"
v-on="on"
@click="resetChangePasswordForm"
@@ -81,42 +77,19 @@
</v-form>
</v-dialog>
<h2 class="headline font-weight-bold mb-3">
Preferences
</h2>
<h3 class="font-weight-bold">
Dark Mode
</h3>
<v-switch
v-model="$vuetify.theme.dark"
:label="`${$vuetify.theme.dark ? 'On' : 'Off'}`"
label="Dark Mode"
@change="toggleDarkMode"
/>
<h3 class="mb-5 font-weight-bold">
Accent Color
</h3>
<v-color-picker
v-model="accentColor"
/>
<v-btn
class="mt-5"
@click="updateAccentColor"
>
Apply
</v-btn>
<v-snackbar
v-model="snackbar"
timeout="3000"
>
{{ snackbarText }}
<template #action="{ attrs }">
<template v-slot:action="{ attrs }">
<v-btn
text
v-bind="attrs"

View File

@@ -12,7 +12,7 @@
v-model="registerUserDialog"
max-width="600px"
>
<template #activator="{ on, attrs }">
<template v-slot:activator="{ on, attrs }">
<v-btn
v-bind="attrs"
v-on="on"
@@ -139,7 +139,7 @@
>
{{ snackbarText }}
<template #action="{ attrs }">
<template v-slot:action="{ attrs }">
<v-btn
text
v-bind="attrs"

View File

@@ -7,7 +7,7 @@
dense
>
<v-app-bar-nav-icon
@click="checkForAdmin"
@click="getCurrentUser"
/>
</v-app-bar>
<v-navigation-drawer
@@ -63,7 +63,7 @@
</v-list-item>
</v-list-item-group>
</v-list>
<template #append>
<template v-slot:append>
<v-btn
block
href="https://github.com/kaythomas0/noisedash"

View File

@@ -50,7 +50,7 @@
>
{{ snackbarText }}
<template #action="{ attrs }">
<template v-slot:action="{ attrs }">
<v-btn
text
v-bind="attrs"

View File

@@ -14,7 +14,7 @@
<v-row justify="center">
<v-btn
:disabled="playDisabled || !isTimerValid || (loadedSamples.length != 0 && !isSporadicValid)"
:disabled="playDisabled || !isTimerValid"
class="mx-3 mb-5"
fab
large
@@ -50,7 +50,7 @@
label="Profiles"
class="mx-3 mb-5"
:disabled="playDisabled"
@change="loadProfile(true)"
@change="loadProfile"
/>
</v-row>
@@ -66,14 +66,14 @@
class="mx-3 my-3"
@click="updateProfile"
>
{{ unsavedWork ? 'Save Profile*' : 'Save Profile' }}
Save Profile
</v-btn>
<v-dialog
v-model="profileDialog"
max-width="600px"
>
<template #activator="{ on, attrs }">
<template v-slot:activator="{ on, attrs }">
<v-btn
v-bind="attrs"
class="mx-3 my-3"
@@ -90,16 +90,18 @@
>
<v-card>
<v-card-title>
<span class="text-h5">Save Profile As...</span>
<span class="text-h5">Profile Name</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-text-field
v-model="profileName"
label="Profile Name"
:rules="[rules.required()]"
/>
<v-col cols="12">
<v-text-field
v-model="profileName"
label="Profile Name"
:rules="[rules.required()]"
/>
</v-col>
</v-row>
</v-container>
</v-card-text>
@@ -127,7 +129,7 @@
v-model="profileMoreDialog"
max-width="600px"
>
<template #activator="{ on, attrs }">
<template v-slot:activator="{ on, attrs }">
<v-btn
v-bind="attrs"
class="mx-3 my-3"
@@ -166,16 +168,6 @@
Export Profile
</v-list-item-title>
</v-list-item>
<v-list-item
@click="openStartRecordingDialog"
>
<v-list-item-icon>
<v-icon>mdi-record</v-icon>
</v-list-item-icon>
<v-list-item-title>
Record Profile Audio
</v-list-item-title>
</v-list-item>
</v-list-item-group>
</v-col>
</v-row>
@@ -260,6 +252,7 @@
:items="profileItems"
return-object
label="Profiles"
class="mx-3 mb-5"
/>
</v-row>
</v-container>
@@ -281,120 +274,6 @@
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
v-model="startRecordingDialog"
max-width="600px"
>
<v-form
ref="startRecordingForm"
v-model="isRecordingValid"
>
<v-card>
<v-card-title>
<span class="text-h5">Start Recording</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<p>Select profile to record audio for. This is only supported on Chrome and Firefox. Current profile will be saved before recording.</p>
</v-row>
<v-row>
<v-select
v-model="recordedProfile"
:items="profileItems"
return-object
label="Profiles"
/>
</v-row>
<v-row>
<v-text-field
v-model="recordingFileName"
label="File Name"
:rules="[rules.required()]"
/>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
text
@click="startRecordingDialog = false"
>
Close
</v-btn>
<v-btn
text
:disabled="!isRecordingValid"
@click="startRecording"
>
Record
</v-btn>
</v-card-actions>
</v-card>
</v-form>
</v-dialog>
<v-dialog
v-model="recordingDialog"
max-width="600px"
persistent
>
<v-card>
<v-card-title>
<span class="text-h5">Recording</span>
</v-card-title>
<v-card-text>
Time Elapsed: {{ recordingTimeElapsed }} Seconds
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
text
@click="cancelRecording"
>
Cancel
</v-btn>
<v-btn
text
@click="stopRecording"
>
Stop and Save
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog
v-model="confirmSwitchProfileDialog"
max-width="600px"
persistent
>
<v-card>
<v-card-title>
<span class="text-h5">Save Profile?</span>
</v-card-title>
<v-card-text>
You have unsaved work on your current profile. Would you like to save it before switching?
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn
text
@click="discardChanges"
>
Discard Changes
</v-btn>
<v-btn
text
@click="saveChanges"
>
Save Profile
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-col>
<v-col cols="12">
@@ -466,7 +345,7 @@
label="Volume"
thumb-label
max="0"
min="-60"
min="-30"
class="mx-3"
@input="updateVolume"
/>
@@ -652,32 +531,24 @@
<v-row
justify="center"
>
<v-col />
<v-col>
<h2 class="mb-5">
{{ sample.name }}
</h2>
</v-col>
<v-col>
<v-btn
icon
:disabled="playDisabled"
@click="removeSample(index)"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
</v-col>
{{ sample.name }}
</v-row>
<v-row>
<v-btn
icon
:disabled="playDisabled"
@click="removeSample(index)"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
<v-slider
v-model="sample.volume"
label="Volume"
thumb-label
max="0"
min="-60"
min="-30"
class="mx-3"
@input="updateSampleVolume(sample.id, index)"
/>
@@ -687,142 +558,6 @@
<p>{{ loadedSamples[index].volume }}</p>
</div>
</v-row>
<v-row
justify="center"
>
<h3 class="font-weight-regular mb-9">
Effects
</h3>
</v-row>
<v-expansion-panels class="mb-9">
<v-expansion-panel>
<v-expansion-panel-header>
Reverb
</v-expansion-panel-header>
<v-expansion-panel-content>
<v-row justify="center">
<v-checkbox
v-model="sample.reverbEnabled"
:disabled="playDisabled"
label="Enabled"
/>
</v-row>
<v-row justify="center">
<v-slider
v-model="sample.reverbPreDelay"
:disabled="playDisabled || !sample.reverbEnabled"
label="Pre Delay"
thumb-label
max="16"
min="0"
step="0.5"
class="mx-3"
/>
<div
class="mx-3"
>
<p>{{ sample.reverbPreDelay }}</p>
</div>
</v-row>
<v-row justify="center">
<v-slider
v-model="sample.reverbDecay"
:disabled="playDisabled || !sample.reverbEnabled"
label="Decay"
thumb-label
max="16"
min="0"
step="0.5"
class="mx-3"
@input="updateVolume"
/>
<div
class="mx-3"
>
<p> {{ sample.reverbDecay }}</p>
</div>
</v-row>
<v-row justify="center">
<v-slider
v-model="sample.reverbWet"
:disabled="playDisabled || !sample.reverbEnabled"
label="Wet"
thumb-label
max="1"
min="0"
step="0.01"
class="mx-3"
@input="updateVolume"
/>
<div
class="mx-3"
>
<p>{{ sample.reverbWet }}%</p>
</div>
</v-row>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
<v-row justify="center">
<h3 class="font-weight-regular">
Playback Mode
</h3>
</v-row>
<v-row
class="mb-5"
>
<v-radio-group
v-model="sample.playbackMode"
:disabled="playDisabled"
mandatory
>
<v-radio
label="Continuous (Looped)"
value="continuous"
/>
<v-radio
label="Sporadic (Not Looped, Plays Randomly Within Interval)"
value="sporadic"
/>
</v-radio-group>
</v-row>
<v-form
v-model="isSporadicValid"
>
<v-row
justify="center"
>
<v-text-field
v-model="sample.sporadicMin"
type="number"
label="Sporadic Min"
class="mx-3"
:disabled="sample.playbackMode != 'sporadic' || playDisabled"
:rules="[rules.gt(-1)]"
/>
<v-text-field
v-model="sample.sporadicMax"
type="number"
label="Sporadic Max"
class="mx-3"
:disabled="sample.playbackMode != 'sporadic' || playDisabled"
:rules="[rules.gt(0)]"
/>
</v-row>
</v-form>
<v-divider
class="mt-7"
/>
</v-container>
</v-row>
@@ -830,7 +565,7 @@
v-model="addSampleDialog"
max-width="600px"
>
<template #activator="{ on, attrs }">
<template v-slot:activator="{ on, attrs }">
<v-btn
v-bind="attrs"
class="mx-3 my-3 mb-5"
@@ -890,7 +625,7 @@
v-model="uploadSampleDialog"
max-width="600px"
>
<template #activator="{ on, attrs }">
<template v-slot:activator="{ on, attrs }">
<v-btn
v-bind="attrs"
class="mx-3 my-3 mb-5"
@@ -911,7 +646,9 @@
<v-card-text>
<v-container>
<v-row>
<p><strong>WARNING:</strong> Uploaded samples are publicly accessible.</p>
<v-col cols="12">
<p><strong>WARNING:</strong> Uploaded samples are publicly accessible.</p>
</v-col>
</v-row>
<v-row>
<v-file-input
@@ -922,11 +659,13 @@
/>
</v-row>
<v-row>
<v-text-field
v-model="sampleName"
label="Sample Name"
:rules="[rules.required()]"
/>
<v-col cols="12">
<v-text-field
v-model="sampleName"
label="Sample Name"
:rules="[rules.required()]"
/>
</v-col>
</v-row>
</v-container>
</v-card-text>
@@ -954,7 +693,7 @@
v-model="editSampleDialog"
max-width="600px"
>
<template #activator="{ on, attrs }">
<template v-slot:activator="{ on, attrs }">
<v-btn
v-bind="attrs"
class="mx-3 my-3 mb-5"
@@ -972,7 +711,7 @@
>
<v-card>
<v-card-title>
<span class="text-h5">Edit Samples</span>
<span class="text-h5">Edit Sample</span>
</v-card-title>
<v-card-text>
<v-container>
@@ -983,6 +722,7 @@
item-text="name"
return-object
label="Samples"
class="mx-3"
@change="loadPreviewSample"
/>
</v-row>
@@ -995,8 +735,8 @@
<v-checkbox
v-model="previewSampleLoopPointsEnabled"
:disabled="previewSamplePlaying"
label="Use Loop Points (Continuous Playback Mode Only)"
@change="updatePreviewSampleLoopPoints"
label="Use Loop Points"
class="mx-3"
/>
</v-row>
@@ -1005,7 +745,7 @@
v-model="previewSampleLoopStart"
type="number"
label="Loop Start Time"
class="mr-3"
class="mx-3"
:disabled="!previewSampleLoopPointsEnabled || previewSamplePlaying"
:rules="[rules.gt(-1)]"
@change="updatePreviewSamplePlayerLoopPoints"
@@ -1015,7 +755,7 @@
v-model="previewSampleLoopEnd"
type="number"
label="Loop End Time"
class="ml-3"
class="mx-3"
:disabled="!previewSampleLoopPointsEnabled || previewSamplePlaying"
:rules="[rules.gt(-1), rules.lt(previewSampleLength)]"
@change="updatePreviewSamplePlayerLoopPoints"
@@ -1026,7 +766,8 @@
<v-text-field
v-model="previewSampleFadeIn"
type="number"
label="Fade In Time (In continuous mode, this only effects first playback)"
label="Fade In Time"
class="mx-3"
:disabled="previewSamplePlaying"
:rules="[rules.gt(-1)]"
@change="updatePreviewSamplePlayerFadeIn"
@@ -1071,7 +812,7 @@
>
{{ infoSnackbarText }}
<template #action="{ attrs }">
<template v-slot:action="{ attrs }">
<v-btn
text
v-bind="attrs"
@@ -1089,7 +830,7 @@
>
{{ errorSnackbarText }}
<template #action="{ attrs }">
<template v-slot:action="{ attrs }">
<v-btn
text
v-bind="attrs"

View File

@@ -1,12 +1,11 @@
export default {
name: 'Account',
name: 'Admin',
data: () => ({
currentUser: {},
changePasswordDialog: false,
isPasswordValid: false,
password: '',
accentColor: {},
snackbar: false,
snackbarText: '',
rules: {
@@ -22,7 +21,6 @@ export default {
.then(response => {
if (response.status === 200) {
this.currentUser = response.data.user
this.accentColor = this.currentUser.preferences.accentColor
}
})
},
@@ -47,14 +45,6 @@ export default {
this.$http.patch('/users/dark-mode', {
darkMode: this.$vuetify.theme.dark
})
},
updateAccentColor () {
const preferences = { accentColor: this.accentColor }
this.$http.patch('/users/preferences', {
preferences: preferences
})
this.$vuetify.theme.themes.dark.primary = this.accentColor.hex
this.$vuetify.theme.themes.light.primary = this.accentColor.hex
}
}
}

View File

@@ -6,9 +6,6 @@ export default {
isAdmin: false,
loggedIn: false
}),
created () {
this.getUserPreferences()
},
methods: {
home () {
this.$router.push('/')
@@ -27,7 +24,7 @@ export default {
}
})
},
checkForAdmin () {
getCurrentUser () {
this.loggedIn = false
this.drawyer = true
this.$http.get('/users/current')
@@ -35,21 +32,12 @@ export default {
if (response.status === 200) {
this.loggedIn = true
this.isAdmin = response.data.user.isAdmin
this.$vuetify.theme.dark = response.data.user.darkMode
}
})
.catch(() => {
this.isAdmin = false
})
},
getUserPreferences () {
this.$http.get('/users/current')
.then(response => {
if (response.status === 200) {
const preferences = response.data.user.preferences
this.$vuetify.theme.themes.dark.primary = preferences.accentColor.hex
this.$vuetify.theme.themes.light.primary = preferences.accentColor.hex
}
})
}
}
}

View File

@@ -1,6 +1,4 @@
export default {
name: 'Login',
data: () => ({
valid: false,
username: '',

View File

@@ -1,5 +1,4 @@
import * as Tone from 'tone'
import { setMediaSessionMetadata } from '@/registerServiceWorker'
export default {
name: 'Noise',
@@ -65,18 +64,6 @@ export default {
previewSampleButtonText: 'Preview Sample',
previewSampleLoading: true,
previewSampleLength: 0,
startRecordingDialog: false,
recordingDialog: false,
recordingTimeElapsed: 0,
recordedProfile: {},
recordingFileName: '',
isRecordingValid: false,
unsavedWork: false,
saveProfileText: 'Save Profile',
unwatch: null,
confirmSwitchProfileDialog: false,
activeProfile: {},
isSporadicValid: false,
errorSnackbar: false,
errorSnackbarText: '',
rules: {
@@ -101,40 +88,6 @@ export default {
}
})
return samples
},
changeableSettings: function () {
const settings = [
this.isTimerEnabled,
this.hours,
this.minutes,
this.seconds,
this.volume,
this.noiseColor,
this.isFilterEnabled,
this.filterType,
this.filterCutoff,
this.isLFOFilterCutoffEnabled,
this.lfoFilterCutoffFrequency,
this.lfoFilterCutoffRange,
this.isTremoloEnabled,
this.tremoloDepth,
this.tremoloFrequency,
this.isTimerEnabled,
this.loadedSamples
]
this.loadedSamples.forEach(s => {
settings.push(s.volume)
settings.push(s.reverbEnabled)
settings.push(s.reverbPreDelay)
settings.push(s.reverbDecay)
settings.push(s.reverbWet)
settings.push(s.playbackMode)
settings.push(s.sporadicMin)
settings.push(s.sporadicMax)
})
return settings
}
},
created () {
@@ -145,7 +98,6 @@ export default {
this.players = new Tone.Players()
this.samplePreviewPlayer = new Tone.Player().toDestination()
this.samplePreviewPlayer.loop = true
this.recorder = new Tone.Recorder()
this.populateProfileItems(0)
this.populatePreviewSampleItems()
@@ -156,13 +108,11 @@ export default {
this.stop()
},
methods: {
async play () {
play () {
if (!this.players.loaded) {
return
}
await Tone.start()
this.playDisabled = true
Tone.Transport.cancel()
@@ -189,84 +139,35 @@ export default {
this.lfo.connect(this.filter.frequency).start()
}
if (this.isTimerEnabled) {
this.duration = parseInt((this.hours * 3600)) + parseInt((this.minutes * 60)) + parseInt(this.seconds)
this.timeRemaining = this.duration
this.transportInterval = setInterval(() => this.stop(), this.duration * 1000 + 100)
this.timeRemainingInterval = setInterval(() => this.startTimer(), 1000)
Tone.Transport.loopEnd = this.duration
this.noise.sync().start(0).stop(this.duration)
} else {
this.noise.sync().start(0)
}
this.loadedSamples.forEach(s => {
this.players.player(s.id).loop = true
this.players.player(s.id).fadeIn = s.fadeIn
if (s.loopPointsEnabled) {
this.players.player(s.id).setLoopPoints(s.loopStart, s.loopEnd)
} else {
this.players.player(s.id).setLoopPoints(0, this.players.player(s.id).buffer.duration)
}
this.players.player(s.id).volume.value = s.volume
this.players.player(s.id).disconnect()
if (s.reverbEnabled) {
const reverb = new Tone.Reverb(s.reverbDecay).toDestination()
reverb.set({ preDelay: s.reverbPreDelay, wet: s.reverbWet })
this.players.player(s.id).connect(reverb)
} else {
this.players.player(s.id).toDestination()
}
if (s.playbackMode === 'sporadic') {
this.players.player(s.id).loop = false
const maxInt = parseInt(s.sporadicMax, 10)
const minInt = parseInt(s.sporadicMin, 10)
if (minInt <= maxInt) {
const rand = Math.floor(Math.random() * (maxInt - minInt + 1) + minInt)
s.initialSporadicPlayInterval = setInterval(() => this.playSporadicSample(s.id), rand * 1000)
}
} else {
this.players.player(s.id).loop = true
if (this.isTimerEnabled) {
this.players.player(s.id).unsync().sync().start(0).stop(this.duration)
} else {
this.players.player(s.id).unsync().sync().start(0)
}
}
})
Tone.Transport.start('+0.1')
if (this.isTimerEnabled) {
this.duration = parseInt((this.hours * 3600)) + parseInt((this.minutes * 60)) + parseInt(this.seconds)
this.noise.sync().start(0).stop(this.duration)
Tone.Transport.loopEnd = this.duration
this.timeRemaining = this.duration
this.transportInterval = setInterval(() => this.stop(), this.duration * 1000 + 100)
this.timeRemainingInterval = setInterval(() => this.startTimer(), 1000)
// Update Media Session so playback can continue in background with proper metadata
try {
setMediaSessionMetadata({ title: (this.selectedProfile && this.selectedProfile.text) ? this.selectedProfile.text : 'Noisedash' })
if ('mediaSession' in navigator) {
navigator.mediaSession.playbackState = 'playing'
navigator.mediaSession.setActionHandler('play', () => this.play())
navigator.mediaSession.setActionHandler('pause', () => this.stop())
navigator.mediaSession.setActionHandler('stop', () => this.stop())
}
} catch (e) { /* no-op */ }
},
playSporadicSample (id) {
const sample = this.loadedSamples.find(s => s.id === id)
this.loadedSamples.forEach(s => {
this.players.player(s.id).unsync().sync().start(0).stop(this.duration)
})
} else {
this.noise.sync().start(0)
clearInterval(sample.initialSporadicPlayInterval)
clearInterval(sample.sporadicInterval)
this.loadedSamples.forEach(s => {
this.players.player(s.id).unsync().sync().start(0)
})
}
this.players.player(id).unsync().sync().start()
const maxInt = parseInt(sample.sporadicMax, 10)
const minInt = parseInt(sample.sporadicMin, 10)
sample.playNextTime = Math.floor(Math.random() * (maxInt - minInt + 1) + minInt)
sample.sporadicInterval = setInterval(() => this.playSporadicSample(id), sample.playNextTime * 1000)
Tone.Transport.start()
},
stop () {
clearInterval(this.transportInterval)
@@ -276,18 +177,6 @@ export default {
clearInterval(this.timeRemainingInterval)
this.timeRemaining = 0
this.duration = 0
this.loadedSamples.forEach(s => {
if (s.playbackMode === 'sporadic') {
clearInterval(s.initialSporadicPlayInterval)
clearInterval(s.sporadicInterval)
}
})
// Reflect playback state for OS controls
if ('mediaSession' in navigator) {
try { navigator.mediaSession.playbackState = 'paused' } catch (e) { /* no-op */ }
}
},
startTimer () {
this.timeRemaining -= 1
@@ -360,8 +249,7 @@ export default {
this.selectedProfile = this.profileItems.find(p => p.id === profileId)
}
this.exportedProfile = this.profileItems[0]
this.recordedProfile = this.profileItems[0]
this.loadProfile(true)
this.loadProfile()
}
}
})
@@ -398,7 +286,6 @@ export default {
if (response.status === 200) {
this.profileDialog = false
this.populateProfileItems(response.data.id)
this.unsavedWork = false
this.infoSnackbarText = 'Profile Saved'
this.infoSnackbar = true
}
@@ -427,7 +314,6 @@ export default {
samples: this.loadedSamples
}).then(response => {
if (response.status === 200) {
this.unsavedWork = false
this.infoSnackbarText = 'Profile Saved'
this.infoSnackbar = true
}
@@ -437,48 +323,34 @@ export default {
this.errorSnackbar = true
})
},
loadProfile (checkForUnsavedWork) {
if (checkForUnsavedWork && this.unsavedWork) {
this.confirmSwitchProfileDialog = true
} else {
this.$http.get('/profiles/'.concat(this.selectedProfile.id))
.then(response => {
if (response.status === 200) {
const profile = response.data.profile
loadProfile () {
this.$http.get('/profiles/'.concat(this.selectedProfile.id))
.then(response => {
if (response.status === 200) {
const profile = response.data.profile
this.isTimerEnabled = profile.isTimerEnabled
this.duration = profile.duration
this.volume = profile.volume
this.noiseColor = profile.noiseColor
this.isFilterEnabled = profile.isFilterEnabled
this.filterType = profile.filterType
this.filterCutoff = profile.filterCutoff
this.isLFOFilterCutoffEnabled = profile.isLFOFilterCutoffEnabled
this.lfoFilterCutoffFrequency = profile.lfoFilterCutoffFrequency
this.lfoFilterCutoffRange[0] = profile.lfoFilterCutoffLow
this.lfoFilterCutoffRange[1] = profile.lfoFilterCutoffHigh
this.isTremoloEnabled = profile.isTremoloEnabled
this.tremoloFrequency = profile.tremoloFrequency
this.tremoloDepth = profile.tremoloDepth
this.isTimerEnabled = profile.isTimerEnabled
this.duration = profile.duration
this.volume = profile.volume
this.noiseColor = profile.noiseColor
this.isFilterEnabled = profile.isFilterEnabled
this.filterType = profile.filterType
this.filterCutoff = profile.filterCutoff
this.isLFOFilterCutoffEnabled = profile.isLFOFilterCutoffEnabled
this.lfoFilterCutoffFrequency = profile.lfoFilterCutoffFrequency
this.lfoFilterCutoffRange[0] = profile.lfoFilterCutoffLow
this.lfoFilterCutoffRange[1] = profile.lfoFilterCutoffHigh
this.isTremoloEnabled = profile.isTremoloEnabled
this.tremoloFrequency = profile.tremoloFrequency
this.tremoloDepth = profile.tremoloDepth
this.loadedSamples = profile.samples
this.activeProfile = profile
if (this.unwatch) {
this.unwatch()
}
this.unwatch = this.$watch('changeableSettings', function () {
this.unsavedWork = true
})
}
})
.catch(() => {
this.errorSnackbarText = 'Error Loading Profile'
this.errorSnackbar = true
})
}
this.loadedSamples = profile.samples
}
})
.catch(() => {
this.errorSnackbarText = 'Error Loading Profile'
this.errorSnackbar = true
})
},
deleteProfile () {
this.$http.delete('/profiles/'.concat(this.selectedProfile.id))
@@ -542,8 +414,6 @@ export default {
this.checkedSamples.forEach(i => {
const load = this.allSamples.find(e => e.id === i)
load.volume = -10
load.sporadicMin = 30
load.sporadicMax = 300
this.loadedSamples.push(load)
})
@@ -563,9 +433,6 @@ export default {
if (response.status === 200) {
this.canUpload = response.data.user.canUpload
this.$vuetify.theme.dark = response.data.user.darkMode
const preferences = response.data.user.preferences
this.$vuetify.theme.themes.dark.primary = preferences.accentColor.hex
this.$vuetify.theme.themes.light.primary = preferences.accentColor.hex
}
})
},
@@ -616,7 +483,7 @@ export default {
}
})
.catch(() => {
this.errorSnackbarText = 'Error Importing Profile'
this.errorSnackbarText = 'Error Saving Profile'
this.errorSnackbar = true
})
@@ -669,7 +536,7 @@ export default {
}
})
.catch(() => {
this.errorSnackbarText = 'Error Exporting Profile'
this.errorSnackbarText = 'Error Loading Profile'
this.errorSnackbar = true
})
@@ -726,13 +593,6 @@ export default {
}
})
},
updatePreviewSampleLoopPoints () {
if (this.previewSampleLoopPointsEnabled) {
this.samplePreviewPlayer.setLoopPoints(this.previewSampleLoopStart, this.previewSampleLoopEnd)
} else {
this.samplePreviewPlayer.setLoopPoints(0, this.samplePreviewPlayer.buffer.duration)
}
},
previewSample () {
if (this.previewSamplePlaying) {
this.previewSamplePlaying = false
@@ -753,16 +613,7 @@ export default {
}).then(response => {
if (response.status === 200) {
this.getSamples()
// Update sample if it's already loaded in current profile
const sample = this.loadedSamples.find(s => s.id === this.selectedPreviewSample.id)
if (sample) {
sample.fadeIn = this.previewSampleFadeIn
sample.loopPointsEnabled = this.previewSampleLoopPointsEnabled
sample.loopStart = this.previewSampleLoopStart
sample.loopEnd = this.previewSampleLoopEnd
}
this.loadProfile()
this.closeEditSampleForm()
this.infoSnackbarText = 'Sample Saved'
this.infoSnackbar = true
@@ -780,161 +631,6 @@ export default {
if (this.previewSampleLoopStart >= 0 && this.previewSampleLoopEnd <= this.previewSampleLength) {
this.samplePreviewPlayer.setLoopPoints(this.previewSampleLoopStart, this.previewSampleLoopEnd)
}
},
openStartRecordingDialog () {
this.startRecordingDialog = true
this.profileMoreDialog = false
},
startRecording () {
// Save current profile before recording
this.updateProfile()
this.$http.get('/profiles/'.concat(this.recordedProfile.id))
.then(async response => {
if (response.status === 200) {
const profile = response.data.profile
this.isTimerEnabled = profile.isTimerEnabled
this.duration = profile.duration
this.volume = profile.volume
this.noiseColor = profile.noiseColor
this.isFilterEnabled = profile.isFilterEnabled
this.filterType = profile.filterType
this.filterCutoff = profile.filterCutoff
this.isLFOFilterCutoffEnabled = profile.isLFOFilterCutoffEnabled
this.lfoFilterCutoffFrequency = profile.lfoFilterCutoffFrequency
this.lfoFilterCutoffRange[0] = profile.lfoFilterCutoffLow
this.lfoFilterCutoffRange[1] = profile.lfoFilterCutoffHigh
this.isTremoloEnabled = profile.isTremoloEnabled
this.tremoloFrequency = profile.tremoloFrequency
this.tremoloDepth = profile.tremoloDepth
this.loadedSamples = profile.samples
this.startRecordingDialog = false
this.recordingDialog = true
this.recordingTimeElapsed = 0
await this.recorder.start()
this.recordingInterval = setInterval(() => this.recordingTimeElapsed++, 1000)
this.playProfileForRecording()
}
})
.catch(() => {
this.errorSnackbarText = 'Error Recording Profile'
this.errorSnackbar = true
})
},
playProfileForRecording () {
this.playDisabled = true
Tone.Transport.cancel()
if (!this.isFilterEnabled && !this.isTremoloEnabled) {
this.noise = new Tone.Noise({ volume: this.volume, type: this.noiseColor }).connect(this.recorder).toDestination()
} else if (!this.isFilterEnabled && this.isTremoloEnabled) {
this.tremolo = new Tone.Tremolo({ frequency: this.tremoloFrequency, depth: this.tremoloDepth }).connect(this.recorder).toDestination().start()
this.noise = new Tone.Noise({ volume: this.volume, type: this.noiseColor }).connect(this.tremolo)
} else if (this.isFilterEnabled && !this.isTremoloEnabled) {
this.filter = new Tone.Filter(this.filterCutoff, this.filterType).connect(this.recorder).toDestination()
this.noise = new Tone.Noise({ volume: this.volume, type: this.noiseColor }).connect(this.filter)
} else if (this.isFilterEnabled && this.isTremoloEnabled) {
this.tremolo = new Tone.Tremolo({ frequency: this.tremoloFrequency, depth: this.tremoloDepth }).connect(this.recorder).toDestination().start()
this.filter = new Tone.Filter(this.filterCutoff, this.filterType).connect(this.tremolo)
this.noise = new Tone.Noise({ volume: this.volume, type: this.noiseColor }).connect(this.filter)
} else {
this.tremolo = new Tone.Tremolo({ frequency: this.tremoloFrequency, depth: this.tremoloDepth }).connect(this.recorder).toDestination().start()
this.filter = new Tone.Filter(this.filterCutoff, this.filterType).connect(this.tremolo)
this.noise = new Tone.Noise({ volume: this.volume, type: this.noiseColor }).connect(this.filter)
}
if (this.isLFOFilterCutoffEnabled) {
this.lfo = new Tone.LFO({ frequency: this.lfoFilterCutoffFrequency, min: this.lfoFilterCutoffRange[0], max: this.lfoFilterCutoffRange[1] })
this.lfo.connect(this.filter.frequency).start()
}
this.loadedSamples.forEach(s => {
this.players.player(s.id).loop = true
this.players.player(s.id).fadeIn = s.fadeIn
if (s.loopPointsEnabled) {
this.players.player(s.id).setLoopPoints(s.loopStart, s.loopEnd)
} else {
this.players.player(s.id).setLoopPoints(0, this.players.player(s.id).buffer.duration)
}
this.players.player(s.id).volume.value = s.volume
this.players.player(s.id).disconnect()
if (s.reverbEnabled) {
const reverb = new Tone.Reverb(s.reverbDecay).connect(this.recorder).toDestination()
reverb.set({ preDelay: s.reverbPreDelay, wet: s.reverbWet })
this.players.player(s.id).connect(reverb)
} else {
this.players.player(s.id).connect(this.recorder).toDestination()
}
})
this.noise.sync().start(0)
this.loadedSamples.forEach(s => {
if (s.playbackMode === 'sporadic') {
this.players.player(s.id).loop = false
const maxInt = parseInt(s.sporadicMax, 10)
const minInt = parseInt(s.sporadicMin, 10)
const rand = Math.floor(Math.random() * (maxInt - minInt + 1) + minInt)
s.initialSporadicPlayInterval = setInterval(() => this.playSporadicSample(s.id), rand * 1000)
} else {
this.players.player(s.id).loop = true
this.players.player(s.id).unsync().sync().start(0)
}
})
Tone.Transport.start('+0.1')
},
async stopRecording () {
const recording = await this.recorder.stop()
// Set active profile back to the selected one
this.loadProfile(false)
const url = URL.createObjectURL(recording)
const anchor = document.createElement('a')
anchor.download = this.recordingFileName + '.webm'
anchor.href = url
anchor.click()
clearInterval(this.recordingInterval)
this.loadedSamples.forEach(s => {
if (s.playbackMode === 'sporadic') {
clearInterval(s.initialSporadicPlayInterval)
clearInterval(s.sporadicInterval)
}
})
this.recordingDialog = false
this.stop()
},
async cancelRecording () {
await this.recorder.stop()
// Set active profile back to the selected one
this.loadProfile(false)
clearInterval(this.recordingInterval)
this.recordingDialog = false
this.stop()
},
discardChanges () {
this.unsavedWork = false
this.loadProfile(true)
this.confirmSwitchProfileDialog = false
},
saveChanges () {
// Set active profile back to previously selected one before saving
this.selectedProfile = this.profileItems.find(p => p.text === this.activeProfile.name)
this.updateProfile()
this.confirmSwitchProfileDialog = false
}
}
}

View File

@@ -1,6 +1,4 @@
export default {
name: 'Register',
data: () => ({
valid: false,
name: '',

View File

@@ -3,7 +3,6 @@ import App from './App.vue'
import router from './router'
import vuetify from './plugins/vuetify'
import instance from './axios'
import './registerServiceWorker'
Vue.prototype.$http = instance

View File

@@ -1,5 +1,5 @@
import Vue from 'vue'
import Vuetify from 'vuetify/lib'
import Vuetify from 'vuetify/lib/framework'
import colors from 'vuetify/lib/util/colors'

View File

@@ -1,46 +0,0 @@
/* global workbox */
// Registers the service worker generated by @vue/cli-plugin-pwa
// This enables installability and offline caching for static assets.
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
const register = async () => {
try {
const reg = await navigator.serviceWorker.register(`${process.env.BASE_URL}service-worker.js`)
// Listen for updates and activate immediately
reg.addEventListener('updatefound', () => {
const newWorker = reg.installing
if (!newWorker) return
newWorker.addEventListener('statechange', () => {
if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
// New content is available; refresh clients
reg.waiting && reg.waiting.postMessage({ type: 'SKIP_WAITING' })
}
})
})
// Claim clients after activation
navigator.serviceWorker.addEventListener('controllerchange', () => {
// optional: location.reload()
})
} catch (e) {
// eslint-disable-next-line no-console
console.warn('SW registration failed', e)
}
}
register()
}
// Minimal Media Session API wiring so playback can continue in background on supported browsers
export function setMediaSessionMetadata (opts = {}) {
if ('mediaSession' in navigator) {
const md = new window.MediaMetadata({
title: opts.title || 'Noisedash',
artist: opts.artist || 'Ambient Generator',
album: opts.album || 'Noisedash',
artwork: opts.artwork || [
{ src: '/favicon.ico', sizes: '64x64', type: 'image/x-icon' }
]
})
navigator.mediaSession.metadata = md
}
}

View File

@@ -1,15 +1,15 @@
<template>
<AccountPage />
<Account />
</template>
<script>
import AccountPage from '../components/AccountPage'
import Account from '../components/Account'
export default {
name: 'AccountView',
components: {
AccountPage
Account
}
}
</script>

View File

@@ -1,15 +1,15 @@
<template>
<AdminPage />
<Admin />
</template>
<script>
import AdminPage from '../components/AdminPage'
import Admin from '../components/Admin'
export default {
name: 'AdminView',
components: {
AdminPage
Admin
}
}
</script>

View File

@@ -1,15 +1,15 @@
<template>
<NoisePage />
<Noise />
</template>
<script>
import NoisePage from '../components/NoisePage'
import Noise from '../components/Noise'
export default {
name: 'HomeView',
components: {
NoisePage
Noise
}
}
</script>

View File

@@ -1,15 +1,15 @@
<template>
<LoginPage />
<Login />
</template>
<script>
import LoginPage from '../components/LoginPage'
import Login from '../components/Login'
export default {
name: 'LoginView',
components: {
LoginPage
Login
}
}
</script>

View File

@@ -1,15 +1,15 @@
<template>
<RegisterPage />
<Register />
</template>
<script>
import RegisterPage from '../components/RegisterPage'
import Register from '../components/Register'
export default {
name: 'RegisterView',
components: {
RegisterPage
Register
}
}
</script>

View File

@@ -3,48 +3,6 @@ module.exports = {
'vuetify'
],
devServer: {
host: '0.0.0.0',
port: 8080,
allowedHosts: 'all',
proxy: 'http://localhost:1432'
},
pwa: {
name: 'Noisedash',
themeColor: '#121212',
backgroundColor: '#121212',
display: 'standalone',
startUrl: '/',
manifestOptions: {
short_name: 'Noisedash',
description: 'Ambient noise generator',
categories: ['music', 'audio', 'productivity'],
display_override: ['standalone', 'browser'],
orientation: 'any'
},
iconPaths: {
favicon32: 'favicon.ico',
favicon16: 'favicon.ico',
appleTouchIcon: 'favicon.ico',
maskIcon: 'favicon.ico',
msTileImage: 'favicon.ico'
},
workboxPluginMode: 'GenerateSW',
workboxOptions: {
cleanupOutdatedCaches: true,
skipWaiting: true,
clientsClaim: true,
offlineGoogleAnalytics: false,
runtimeCaching: [
{
urlPattern: /\.(?:png|jpg|jpeg|svg|gif|webp|mp3|wav|ogg)$/,
handler: 'CacheFirst',
options: {
cacheName: 'assets-cache',
expiration: { maxEntries: 60, maxAgeSeconds: 7 * 24 * 60 * 60 },
cacheableResponse: { statuses: [0, 200] }
}
}
]
}
proxy: 'http://localhost:1432'
}
}