The main website of PHENOM
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. <template>
  2. <v-layout column justify-center align-left>
  3. <v-flex xs12 sm8 md6>
  4. <h3 class="display-3">Bytebeat Generator</h3>
  5. <span class="subheading">This small tool can create music from a single JavaScript Expression using the Web Audio API.</span>
  6. <v-divider class="my-3"></v-divider>
  7. <v-select
  8. v-model="exampleSelected"
  9. prepend-icon="mdi-file-music"
  10. :items="bytebeatSaves"
  11. return-object
  12. item-text="name"
  13. >
  14. </v-select>
  15. <v-btn @click="load()">Load</v-btn>
  16. <v-btn @click="saveDialog = true" :disabled="!changed">Save As...</v-btn>
  17. <v-btn color="red" :disabled="exampleSelected.isExample" @click="deleteSave()">Delete</v-btn>
  18. <v-divider class="my-3"></v-divider>
  19. <v-form>
  20. <v-select
  21. v-model="liveByteBeat.frequency"
  22. prepend-icon="mdi-waves"
  23. :items="frequencies"
  24. label="Frequency (Hz)"
  25. >
  26. </v-select>
  27. <v-text-field
  28. v-model="liveByteBeat.duration"
  29. label="Duration (seconds)"
  30. placeholder="30"
  31. prepend-icon="timer"
  32. ></v-text-field>
  33. <v-text-field
  34. v-model="liveByteBeat.ft"
  35. label="JavaScript function (t)="
  36. 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'
  37. hint="The variable t determines how the function behaves"
  38. ></v-text-field>
  39. <v-slider
  40. v-model="volume"
  41. v-on:input="setVolume"
  42. :disabled="!isContextAvailable()"
  43. label="Volume"
  44. append-icon="volume_up"
  45. prepend-icon="volume_down"
  46. ></v-slider>
  47. <p color="red" v-if="changed">Changes were made. Press on Generate to recompile the expression.</p>
  48. <v-layout row wrap align-center>
  49. <v-btn color="green" @click="regenerate()">
  50. <v-icon>mdi-sync</v-icon>
  51. <span>Generate</span>
  52. </v-btn>
  53. <v-btn-toggle v-model="playBtn" multiple>
  54. <v-btn flat @click="playPause()" :disabled="!isGenerated()">
  55. <v-icon>mdi-play-pause</v-icon>
  56. </v-btn>
  57. </v-btn-toggle>
  58. <v-btn-toggle v-model="muteBtn" multiple>
  59. <v-btn flat @click="mute()" :disabled="!isContextAvailable()">
  60. <v-icon>mdi-volume-off</v-icon>
  61. </v-btn>
  62. </v-btn-toggle>
  63. </v-layout>
  64. </v-form>
  65. <v-dialog
  66. v-model="saveDialog"
  67. max-width="290"
  68. >
  69. <v-card>
  70. <v-card-title class="headline">Saving ByteBeat...</v-card-title>
  71. <v-layout align-content-space-between>
  72. <v-form>
  73. <v-text-field
  74. v-model="newName"
  75. placeholder="Enter a Name"
  76. label="Name"
  77. ></v-text-field>
  78. </v-form>
  79. </v-layout>
  80. <v-card-actions>
  81. <v-spacer></v-spacer>
  82. <v-btn
  83. flat="flat"
  84. @click="saveDialog = false"
  85. >
  86. Cancel
  87. </v-btn>
  88. <v-btn
  89. color="green darken-1"
  90. flat="flat"
  91. @click="onSave()"
  92. >
  93. Save
  94. </v-btn>
  95. </v-card-actions>
  96. </v-card>
  97. </v-dialog>
  98. </v-flex>
  99. </v-layout>
  100. </template>
  101. <script>
  102. import { mapGetters, mapState, mapMutations } from 'vuex'
  103. /* eslint no-eval: 0 */
  104. export default {
  105. layout: 'phenomic',
  106. data: () => ({
  107. exampleSelected: {
  108. id: 0,
  109. isExample: true,
  110. name: 'Default',
  111. frequency: '8000',
  112. duration: '30',
  113. ft: 't * ((t>>12|t>>8)&63&t>>4)'
  114. },
  115. liveByteBeat: {
  116. id: 0,
  117. name: 'Default',
  118. frequency: '8000',
  119. duration: '30',
  120. ft: 't * ((t>>12|t>>8)&63&t>>4)'
  121. },
  122. playBtn: [],
  123. muteBtn: [],
  124. frequencies: ['8000', '11025', '16000', '22050', '32000', '37800', '44056', '44100', '47250', '48000', '50000', '50400', '88200', '96000', '176400', '192000'],
  125. audioCtx: null,
  126. source: null,
  127. gainNode: null,
  128. volume: 50,
  129. sampleBuffer: null,
  130. saveDialog: false,
  131. changed: false,
  132. isLoading: false,
  133. newName: ''
  134. }),
  135. watch: {
  136. liveByteBeat: {
  137. handler: function (newVal, oldVal) {
  138. if (!this.isLoading) {
  139. this.changed = true
  140. } else {
  141. this.changed = false
  142. this.isLoading = false
  143. }
  144. },
  145. deep: true
  146. }
  147. },
  148. errorCaptured (err, vm, info) {
  149. console.log(err)
  150. },
  151. computed: {
  152. ...mapState({
  153. bytebeatSaves: 'bytebeatSaves'
  154. }),
  155. ...mapGetters({
  156. getNewId: 'getNewId'
  157. })
  158. },
  159. methods: {
  160. onChange (newVal) {
  161. console.log('Change: ' + newVal)
  162. this.changed = true
  163. this.liveByteBeat.isExample = false
  164. },
  165. isGenerated () {
  166. if (!(this.source === undefined || this.source === null)) {
  167. return !(this.source.buffer === undefined || this.source.buffer === null)
  168. }
  169. return false
  170. },
  171. isContextAvailable () {
  172. return !(this.audioCtx === undefined || this.audioCtx === null)
  173. },
  174. isExample () {
  175. return this.liveByteBeat.isExample
  176. },
  177. isPlaying () {
  178. if (this.audioCtx.state === 'running') {
  179. return true
  180. } else if (this.audioCtx.state === 'suspended') {
  181. return false
  182. }
  183. },
  184. regenerate () {
  185. // Reset change indicator
  186. this.changed = false
  187. // Reset Playbutton
  188. this.playBtn = []
  189. if (process.browser) {
  190. // Close Audio Context if already opened
  191. if (this.isContextAvailable()) {
  192. this.audioCtx.close()
  193. }
  194. // Create new Audio Context
  195. this.audioCtx = new (window.AudioContext || window.webkitAudioContext)()
  196. this.audioCtx.suspend() // Stop instant playback
  197. this.gainNode = this.audioCtx.createGain()
  198. this.gainNode.connect(this.audioCtx.destination)
  199. } else {
  200. console.error("This is not a browser! Can't continue!")
  201. return
  202. }
  203. // Create a new Audio Source
  204. this.source = this.audioCtx.createBufferSource()
  205. // Determines the behaviour when the Audio has finished playing
  206. this.source.onended = (event) => {
  207. this.audioCtx.suspend()
  208. this.playBtn = []
  209. }
  210. // Here is the main clear on compare match linear function of the bytebeat algorithm
  211. this.sampleBuffer = []
  212. let f
  213. eval('f = function (t) { return ' + this.liveByteBeat.ft + '}')
  214. for (let t = 0; t < this.liveByteBeat.frequency * this.liveByteBeat.duration; t++) {
  215. let sample = f(t)
  216. sample = (sample & 0xff) * 256
  217. if (sample < 0) sample = 0
  218. if (sample > 65535) sample = 65535
  219. this.sampleBuffer.push(sample / 65535)
  220. }
  221. // Fill Audio Buffer with Data
  222. let audioBuffer = this.audioCtx.createBuffer(2, this.liveByteBeat.frequency * this.liveByteBeat.duration, this.liveByteBeat.frequency)
  223. for (let channel = 0; channel < audioBuffer.numberOfChannels; channel++) {
  224. let channelBuffer = audioBuffer.getChannelData(channel)
  225. for (let i = 0; i < audioBuffer.length; i++) {
  226. channelBuffer[i] = this.sampleBuffer[i]
  227. }
  228. }
  229. // Apply Audio Buffer to source
  230. this.source.buffer = audioBuffer
  231. // Apply Gain connection for Volume control
  232. this.source.connect(this.gainNode)
  233. // Start the source
  234. this.source.start(0)
  235. },
  236. playPause () {
  237. if (this.isGenerated()) {
  238. if (this.isPlaying()) {
  239. this.audioCtx.suspend()
  240. this.playBtn = []
  241. } else {
  242. this.audioCtx.resume()
  243. }
  244. }
  245. },
  246. mute () {
  247. if (this.muteBtn.length === 0) {
  248. this.gainNode.gain.value = 0
  249. } else {
  250. this.gainNode.gain.value = this.volume / 100.0
  251. }
  252. },
  253. setVolume (something) {
  254. this.muteBtn = []
  255. this.gainNode.gain.value = something / 100.0
  256. },
  257. onSave () {
  258. this.saveDialog = false
  259. this.liveByteBeat.name = this.newName
  260. this.liveByteBeat.id = this.getNewId
  261. this.saveBeat(this.liveByteBeat)
  262. this.exampleSelected = this.liveByteBeat
  263. this.isCurrentExample = false
  264. },
  265. load () {
  266. this.isLoading = true
  267. this.liveByteBeat.id = this.exampleSelected.id
  268. this.liveByteBeat.duration = this.exampleSelected.duration
  269. this.liveByteBeat.frequency = this.exampleSelected.frequency
  270. this.liveByteBeat.ft = this.exampleSelected.ft
  271. this.liveByteBeat.name = this.exampleSelected.name
  272. this.isCurrentExample = this.exampleSelected.isExample
  273. },
  274. deleteSave () {
  275. if (!this.isCurrentExample) {
  276. this.deleteBeat(this.exampleSelected.id)
  277. }
  278. },
  279. ...mapMutations({
  280. saveBeat: 'saveByteBeat',
  281. deleteBeat: 'deleteByteBeatById'
  282. })
  283. },
  284. beforeRouteLeave (to, from, next) {
  285. // Close Audio Context if already opened
  286. if (this.isContextAvailable()) {
  287. this.audioCtx.close()
  288. }
  289. next()
  290. }
  291. }
  292. </script>