123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- <template>
- <v-layout column justify-center align-left>
- <v-flex xs12 sm8 md6>
- <h3 class="display-3">Bytebeat Generator</h3>
- <span class="subheading">This small tool can create music from a single JavaScript Expression using the Web Audio API.</span>
- <v-divider class="my-3"></v-divider>
- <v-select
- v-model="exampleSelected"
- prepend-icon="mdi-file-music"
- :items="bytebeatSaves"
- return-object
- item-text="name"
- >
- </v-select>
- <v-btn @click="load()">Load</v-btn>
- <v-btn @click="saveDialog = true" :disabled="!changed">Save As...</v-btn>
- <v-btn color="red" :disabled="exampleSelected.isExample" @click="deleteSave()">Delete</v-btn>
- <v-divider class="my-3"></v-divider>
- <v-form>
- <v-select
- v-model="liveByteBeat.frequency"
- prepend-icon="mdi-waves"
- :items="frequencies"
- label="Frequency (Hz)"
- >
- </v-select>
- <v-text-field
- v-model="liveByteBeat.duration"
- label="Duration (seconds)"
- placeholder="30"
- prepend-icon="timer"
- ></v-text-field>
- <v-text-field
- v-model="liveByteBeat.ft"
- label="JavaScript function (t)="
- value='w=t>>9,k=32,m=2048,a=1-t/m%1,d=(14*t*t^t)%m*a,y=[3,3,4.7,2][p=w/k&3]*t/4,h="IQNNNN!!]]!Q!IW]WQNN??!!W]WQNNN?".charCodeAt(w/2&15|p/3<<4)/33*t-t,s=y*.98%80+y%80+(w>>7&&a*((5*t%m*a&128)*(0x53232323>>w/4&1)+(d&127)*(0xa444c444>>w/4&1)*1.5+(d*w&1)+(h%k+h*1.99%k+h*.49%k+h*.97%k-64)*(4-a-a))),s*s>>14?127:s'
- hint="The variable t determines how the function behaves"
- ></v-text-field>
- <v-slider
- v-model="volume"
- v-on:input="setVolume"
- :disabled="!isContextAvailable()"
- label="Volume"
- append-icon="volume_up"
- prepend-icon="volume_down"
- ></v-slider>
- <p color="red" v-if="changed">Changes were made. Press on Generate to recompile the expression.</p>
- <v-layout row wrap align-center>
- <v-btn color="green" @click="regenerate()">
- <v-icon>mdi-sync</v-icon>
- <span>Generate</span>
- </v-btn>
- <v-btn-toggle v-model="playBtn" multiple>
- <v-btn flat @click="playPause()" :disabled="!isGenerated()">
- <v-icon>mdi-play-pause</v-icon>
- </v-btn>
- </v-btn-toggle>
- <v-btn-toggle v-model="muteBtn" multiple>
- <v-btn flat @click="mute()" :disabled="!isContextAvailable()">
- <v-icon>mdi-volume-off</v-icon>
- </v-btn>
- </v-btn-toggle>
- </v-layout>
- </v-form>
- <v-dialog
- v-model="saveDialog"
- max-width="290"
- >
- <v-card>
- <v-card-title class="headline">Saving ByteBeat...</v-card-title>
- <v-layout align-content-space-between>
- <v-form>
- <v-text-field
- v-model="newName"
- placeholder="Enter a Name"
- label="Name"
- ></v-text-field>
- </v-form>
- </v-layout>
- <v-card-actions>
- <v-spacer></v-spacer>
- <v-btn
- flat="flat"
- @click="saveDialog = false"
- >
- Cancel
- </v-btn>
- <v-btn
- color="green darken-1"
- flat="flat"
- @click="onSave()"
- >
- Save
- </v-btn>
- </v-card-actions>
- </v-card>
- </v-dialog>
- </v-flex>
- </v-layout>
- </template>
- <script>
- import { mapGetters, mapState, mapMutations } from 'vuex'
-
- /* eslint no-eval: 0 */
- export default {
- layout: 'phenomic',
- data: () => ({
- exampleSelected: {
- id: 0,
- isExample: true,
- name: 'Default',
- frequency: '8000',
- duration: '30',
- ft: 't * ((t>>12|t>>8)&63&t>>4)'
- },
- liveByteBeat: {
- id: 0,
- name: 'Default',
- frequency: '8000',
- duration: '30',
- ft: 't * ((t>>12|t>>8)&63&t>>4)'
- },
- playBtn: [],
- muteBtn: [],
- frequencies: ['8000', '11025', '16000', '22050', '32000', '37800', '44056', '44100', '47250', '48000', '50000', '50400', '88200', '96000', '176400', '192000'],
- audioCtx: null,
- source: null,
- gainNode: null,
- volume: 50,
- sampleBuffer: null,
- saveDialog: false,
- changed: false,
- isLoading: false,
- newName: ''
- }),
- watch: {
- liveByteBeat: {
- handler: function (newVal, oldVal) {
- if (!this.isLoading) {
- this.changed = true
- } else {
- this.changed = false
- this.isLoading = false
- }
- },
- deep: true
- }
- },
- errorCaptured (err, vm, info) {
- console.log(err)
- },
- computed: {
- ...mapState({
- bytebeatSaves: 'bytebeatSaves'
- }),
- ...mapGetters({
- getNewId: 'getNewId'
- })
- },
- methods: {
- onChange (newVal) {
- console.log('Change: ' + newVal)
- this.changed = true
- this.liveByteBeat.isExample = false
- },
- isGenerated () {
- if (!(this.source === undefined || this.source === null)) {
- return !(this.source.buffer === undefined || this.source.buffer === null)
- }
- return false
- },
- isContextAvailable () {
- return !(this.audioCtx === undefined || this.audioCtx === null)
- },
- isExample () {
- return this.liveByteBeat.isExample
- },
- isPlaying () {
- if (this.audioCtx.state === 'running') {
- return true
- } else if (this.audioCtx.state === 'suspended') {
- return false
- }
- },
- regenerate () {
- // Reset change indicator
- this.changed = false
-
- // Reset Playbutton
- this.playBtn = []
-
- if (process.browser) {
- // Close Audio Context if already opened
- if (this.isContextAvailable()) {
- this.audioCtx.close()
- }
-
- // Create new Audio Context
- this.audioCtx = new (window.AudioContext || window.webkitAudioContext)()
- this.audioCtx.suspend() // Stop instant playback
- this.gainNode = this.audioCtx.createGain()
- this.gainNode.connect(this.audioCtx.destination)
- } else {
- console.error("This is not a browser! Can't continue!")
- return
- }
-
- // Create a new Audio Source
- this.source = this.audioCtx.createBufferSource()
-
- // Determines the behaviour when the Audio has finished playing
- this.source.onended = (event) => {
- this.audioCtx.suspend()
- this.playBtn = []
- }
-
- // Here is the main clear on compare match linear function of the bytebeat algorithm
- this.sampleBuffer = []
- let f
- eval('f = function (t) { return ' + this.liveByteBeat.ft + '}')
- for (let t = 0; t < this.liveByteBeat.frequency * this.liveByteBeat.duration; t++) {
- let sample = f(t)
- sample = (sample & 0xff) * 256
- if (sample < 0) sample = 0
- if (sample > 65535) sample = 65535
- this.sampleBuffer.push(sample / 65535)
- }
-
- // Fill Audio Buffer with Data
- let audioBuffer = this.audioCtx.createBuffer(2, this.liveByteBeat.frequency * this.liveByteBeat.duration, this.liveByteBeat.frequency)
- for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
- let channelBuffer = audioBuffer.getChannelData(channel)
- for (let i = 0; i < audioBuffer.length; i++) {
- channelBuffer[i] = this.sampleBuffer[i]
- }
- }
-
- // Apply Audio Buffer to source
- this.source.buffer = audioBuffer
-
- // Apply Gain connection for Volume control
- this.source.connect(this.gainNode)
-
- // Start the source
- this.source.start(0)
- },
- playPause () {
- if (this.isGenerated()) {
- if (this.isPlaying()) {
- this.audioCtx.suspend()
- this.playBtn = []
- } else {
- this.audioCtx.resume()
- }
- }
- },
- mute () {
- if (this.muteBtn.length === 0) {
- this.gainNode.gain.value = 0
- } else {
- this.gainNode.gain.value = this.volume / 100.0
- }
- },
- setVolume (something) {
- this.muteBtn = []
- this.gainNode.gain.value = something / 100.0
- },
- onSave () {
- this.saveDialog = false
- this.liveByteBeat.name = this.newName
- this.liveByteBeat.id = this.getNewId
- this.saveBeat(this.liveByteBeat)
- this.exampleSelected = this.liveByteBeat
- this.isCurrentExample = false
- },
- load () {
- this.isLoading = true
- this.liveByteBeat.id = this.exampleSelected.id
- this.liveByteBeat.duration = this.exampleSelected.duration
- this.liveByteBeat.frequency = this.exampleSelected.frequency
- this.liveByteBeat.ft = this.exampleSelected.ft
- this.liveByteBeat.name = this.exampleSelected.name
- this.isCurrentExample = this.exampleSelected.isExample
- },
- deleteSave () {
- if (!this.isCurrentExample) {
- this.deleteBeat(this.exampleSelected.id)
- }
- },
- ...mapMutations({
- saveBeat: 'saveByteBeat',
- deleteBeat: 'deleteByteBeatById'
- })
- },
- beforeRouteLeave (to, from, next) {
- // Close Audio Context if already opened
- if (this.isContextAvailable()) {
- this.audioCtx.close()
- }
- next()
- }
- }
- </script>
|