Compare commits

..

13 Commits

Author SHA1 Message Date
Kay Thomas
f8289ad4d1 Merge pull request #17 from kaythomas0/dev
v0.3.0
2022-05-01 19:33:24 -07:00
Kevin Thomas
5fb7066e59 Update README 2022-04-30 23:19:36 -07:00
Kay Thomas
6cc6493ee6 Merge pull request #16 from kaythomas0/kaythomas0-patch-1
Update docker-image.yml
2022-04-30 23:15:58 -07:00
Kay Thomas
c97c87f58c Update docker-image.yml 2022-04-30 23:13:07 -07:00
Kay Thomas
4bc985d36e Update docker-image.yml 2022-04-30 23:10:46 -07:00
Kay Thomas
c6ff0b6def Update docker-image.yml 2022-04-30 22:29:04 -07:00
Kay Thomas
319be778b0 Merge pull request #15 from kaythomas0/github-workflow
GitHub workflow
2022-04-30 21:54:13 -07:00
Kay Thomas
86141a124c Create github workflow 2022-04-30 21:53:06 -07:00
Kevin Thomas
8ad174b0af Add recording function improvements 2022-04-30 21:07:41 -07:00
Kevin Thomas
c485b0a786 Update README 2022-04-29 00:08:31 -07:00
Kevin Thomas
e13a056706 Implement record function 2022-04-28 23:56:51 -07:00
Kevin Thomas
232163064e Start implementing record function 2022-04-26 19:36:33 -07:00
Kay Thomas
5a5660c361 Merge pull request #14 from kaythomas0/dev
v0.2.0
2022-04-17 16:22:30 -07:00
5 changed files with 275 additions and 32 deletions

42
.github/workflows/docker-image.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Docker Image CI
on:
push:
branches: [ main ]
jobs:
buildx:
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 linux/amd64
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/amd64
push: true
tags: noisedash/noisedash:latest
-
name: Build and push linux/arm/v7
uses: docker/build-push-action@v2
with:
context: .
platforms: linux/arm/v7
push: true
tags: noisedash/noisedash:latest-armv7

View File

@@ -9,9 +9,9 @@ Self-hostable web tool for generating ambient noises
# 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
* Save "noise profiles" so you can easily switch between your created soundscapes. Import and export them for easy sharing, record them for use elsewhere
* Fine-tune your noises with audio processing tools like filters, LFOs, and effects
* Upload audio samples (e.g rain, wind, thunder) to combine with your generated noises
* Upload and edit audio samples (e.g rain, wind, thunder) to combine with your generated noises
* Use admin tools to manage multiple users
* Mobile friendly

View File

@@ -68,5 +68,4 @@ 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

@@ -90,18 +90,16 @@
>
<v-card>
<v-card-title>
<span class="text-h5">Profile Name</span>
<span class="text-h5">Save Profile As...</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-col cols="12">
<v-text-field
v-model="profileName"
label="Profile Name"
:rules="[rules.required()]"
/>
</v-col>
<v-text-field
v-model="profileName"
label="Profile Name"
:rules="[rules.required()]"
/>
</v-row>
</v-container>
</v-card-text>
@@ -168,6 +166,16 @@
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>
@@ -252,7 +260,6 @@
:items="profileItems"
return-object
label="Profiles"
class="mx-3 mb-5"
/>
</v-row>
</v-container>
@@ -274,6 +281,90 @@
</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.</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="true"
>
<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-col>
<v-col cols="12">
@@ -345,7 +436,7 @@
label="Volume"
thumb-label
max="0"
min="-30"
min="-60"
class="mx-3"
@input="updateVolume"
/>
@@ -548,7 +639,7 @@
label="Volume"
thumb-label
max="0"
min="-30"
min="-60"
class="mx-3"
@input="updateSampleVolume(sample.id, index)"
/>
@@ -646,9 +737,7 @@
<v-card-text>
<v-container>
<v-row>
<v-col cols="12">
<p><strong>WARNING:</strong> Uploaded samples are publicly accessible.</p>
</v-col>
<p><strong>WARNING:</strong> Uploaded samples are publicly accessible.</p>
</v-row>
<v-row>
<v-file-input
@@ -659,13 +748,11 @@
/>
</v-row>
<v-row>
<v-col cols="12">
<v-text-field
v-model="sampleName"
label="Sample Name"
:rules="[rules.required()]"
/>
</v-col>
<v-text-field
v-model="sampleName"
label="Sample Name"
:rules="[rules.required()]"
/>
</v-row>
</v-container>
</v-card-text>
@@ -711,7 +798,7 @@
>
<v-card>
<v-card-title>
<span class="text-h5">Edit Sample</span>
<span class="text-h5">Edit Samples</span>
</v-card-title>
<v-card-text>
<v-container>
@@ -722,7 +809,6 @@
item-text="name"
return-object
label="Samples"
class="mx-3"
@change="loadPreviewSample"
/>
</v-row>
@@ -736,7 +822,6 @@
v-model="previewSampleLoopPointsEnabled"
:disabled="previewSamplePlaying"
label="Use Loop Points"
class="mx-3"
/>
</v-row>
@@ -745,7 +830,7 @@
v-model="previewSampleLoopStart"
type="number"
label="Loop Start Time"
class="mx-3"
class="mr-3"
:disabled="!previewSampleLoopPointsEnabled || previewSamplePlaying"
:rules="[rules.gt(-1)]"
@change="updatePreviewSamplePlayerLoopPoints"
@@ -755,7 +840,7 @@
v-model="previewSampleLoopEnd"
type="number"
label="Loop End Time"
class="mx-3"
class="ml-3"
:disabled="!previewSampleLoopPointsEnabled || previewSamplePlaying"
:rules="[rules.gt(-1), rules.lt(previewSampleLength)]"
@change="updatePreviewSamplePlayerLoopPoints"
@@ -767,7 +852,6 @@
v-model="previewSampleFadeIn"
type="number"
label="Fade In Time"
class="mx-3"
:disabled="previewSamplePlaying"
:rules="[rules.gt(-1)]"
@change="updatePreviewSamplePlayerFadeIn"

View File

@@ -64,6 +64,12 @@ export default {
previewSampleButtonText: 'Preview Sample',
previewSampleLoading: true,
previewSampleLength: 0,
startRecordingDialog: false,
recordingDialog: false,
recordingTimeElapsed: 0,
recordedProfile: {},
recordingFileName: '',
isRecordingValid: false,
errorSnackbar: false,
errorSnackbarText: '',
rules: {
@@ -98,6 +104,7 @@ 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()
@@ -249,6 +256,7 @@ export default {
this.selectedProfile = this.profileItems.find(p => p.id === profileId)
}
this.exportedProfile = this.profileItems[0]
this.recordedProfile = this.profileItems[0]
this.loadProfile()
}
}
@@ -483,7 +491,7 @@ export default {
}
})
.catch(() => {
this.errorSnackbarText = 'Error Saving Profile'
this.errorSnackbarText = 'Error Importing Profile'
this.errorSnackbar = true
})
@@ -536,7 +544,7 @@ export default {
}
})
.catch(() => {
this.errorSnackbarText = 'Error Loading Profile'
this.errorSnackbarText = 'Error Exporting Profile'
this.errorSnackbar = true
})
@@ -631,6 +639,116 @@ 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 () {
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)
}
this.players.player(s.id).volume.value = s.volume
this.players.player(s.id).connect(this.recorder)
this.players.player(s.id).unsync().sync().start(0)
})
this.noise.sync().start(0)
Tone.Transport.start()
},
async stopRecording () {
const recording = await this.recorder.stop()
// Set active profile back to the selected one
this.loadProfile()
const url = URL.createObjectURL(recording)
const anchor = document.createElement('a')
anchor.download = this.recordingFileName + '.webm'
anchor.href = url
anchor.click()
clearInterval(this.recordingInterval)
this.recordingDialog = false
this.stop()
},
async cancelRecording () {
await this.recorder.stop()
// Set active profile back to the selected one
this.loadProfile()
clearInterval(this.recordingInterval)
this.recordingDialog = false
this.stop()
}
}
}