jcunews

BeepTune.vbs

Feb 14th, 2022 (edited)
362
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
VBScript 15.51 KB | None | 0 0
  1. 'BeepTune v1.0.1, February 2022.
  2. 'https://www.reddit.com/user/jcunews1
  3. 'https://pastebin.com/u/jcunews
  4. 'https://greasyfork.org/en/users/85671-jcunews
  5. '
  6. 'Usage: BeepTune [options] [code]
  7. '
  8. 'Options:
  9. '/i:x  Read input from file x (override code from command line).
  10. '/o:x  Write audio output into file x.
  11. '/r    Render the code only. Does not play.
  12. '
  13. 'Code: one or more events, each separated by space(s).
  14. '
  15. 'Events:
  16. '{note[[+/-]octave]}[.length]
  17. '  Generate a note using default or specified octave and/or length.
  18. '  Notes: c, c#, d, d#, e, f, f#, g, g#, a, a#, b.
  19. '  Octave: 0-9. default=4.
  20. '  Length: 1-999 in beats. default=4. higher = longer.
  21. '  If octave is specified, sets the octave for the note and default octave.
  22. '  If "+"/"-" is specified, octave is changed relative to default octave.
  23. '  If length is specified, sets the length in beats for this note only.
  24. '  e.g.: c, or c5, or c+1, or c.4, or c5.4, or c+1.4, etc.
  25. '{.length}
  26. '  Generate a rest of specified length. See below. e.g.: .16
  27. '{commod{value}}
  28. '  Commands/modifiers:
  29. '    In       Set signal type. 0/else=squareWave, 1=sineWave
  30. '    Ln       Set default note/rest length in beats.
  31. '             default = 4 (4/16). i.e. length 4 = 1 beat, length 2 = 1/2 beat.
  32. '    Mn       Set measure length in beats. 1-9999. 0=1. default = 16 (16/16).
  33. '             Time Signature = measureLength / length.
  34. '    On       Set default octave. 0-9. default = 4 (middle C's octave).
  35. '    Sn       Set speed in beats per minute (BPM). 1-9999. 0=1. default=100.
  36. '    Tn       Set track number. 0-999. default = 0.
  37. '    V[+/-]n  Set volume. 0-100. default=50.
  38. '    Xn       Custom note with frequency n.
  39. '  e.g.: i1 l4 m4 o5 s100 t1 v50 x500
  40. '
  41. 'For file input, empty lines and lines which start with ";", are ignored.
  42.  
  43. 'Changelog:
  44. 'v1.0.1:
  45. '- Initial release.
  46. '- Known problem: clicking noise at start/end of a note.
  47. '  Anyone know how to smoothly transition between two different sine waveform,
  48. '  please contact me.
  49.  
  50. 'note frequency table:
  51. 'https://www.liutaiomottola.com/formulae/freqtab.htm
  52. noteLabels = array("C","C#","D","D#","E","F","F#","G","G#","A","A#","B")
  53. set noteIndexes = createobject("scripting.dictionary")
  54. for i = 0 to 11
  55.   noteIndexes.add noteLabels(i), i
  56. next
  57. 'noteFreqs: [octave, note] [0..9, 0..11] C4=middleC
  58. noteFreqs = array( _
  59.   array(16.351,17.324,18.354,19.445,20.601,21.827, _
  60.     23.124,24.499,25.956,27.5,29.135,30.868), _
  61.   array(32.703,34.648,36.708,38.891,41.203,43.654, _
  62.     46.249,48.999,51.913,55,58.27,61.735), _
  63.   array(65.406,69.296,73.416,77.782,82.407,87.307, _
  64.     92.499,97.999,103.826,110,116.541,123.471), _
  65.   array(130.813,138.591,146.832,155.563,164.814,174.614, _
  66.     184.997,195.998,207.652,220,233.082,246.942), _
  67.   array(261.626,277.183,293.665,311.127,329.628,349.228, _
  68.     369.994,391.995,415.305,440,466.164,493.883), _
  69.   array(523.251,554.365,587.33,622.254,659.255,698.456, _
  70.     739.989,783.991,830.609,880,932.328,987.767), _
  71.   array(1046.502,1108.731,1174.659,1244.508,1318.51,1396.913, _
  72.     1479.978,1567.982,1661.219,1760,1864.655,1975.533), _
  73.   array(2093.005,2217.461,2349.318,2489.016,2637.021,2793.826, _
  74.     2959.955,3135.964,3322.438,3520,3729.31,3951.066), _
  75.   array(4186.009,4434.922,4698.636,4978.032,5274.042,5587.652, _
  76.     5919.91,6271.928,6644.876,7040,7458.62,7902.132), _
  77.   array(8372.018,8869.844,9397.272,9956.064,10548.084,11175.304, _
  78.     11839.82,12543.856,13289.752,14080,14917.24,15804.264) _
  79. )
  80. 'supported rates for stream. !16-bit signed mono PCM only!
  81. sampleRates = array( _
  82.   8000, 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000)
  83.  
  84. 'generate and replace with signal from previous to neutral zero
  85. function genNeutral(byref buf, byref bufPos, sampleRate, seconds, _
  86.   byref prevPhase)
  87.   dim dataLen, i, pdelta
  88.   if not ((sampleRate >= 8000) and (sampleRate <= 48000)) then
  89.     sampleRate = 44100
  90.   end if
  91.   sampleRate = int(sampleRate)
  92.   if not ((seconds >= 0) and (seconds <= 1.79769313486231580779e308)) then
  93.     seconds = 1
  94.   end if
  95.   dataLen = sampleRate * seconds
  96.   redim preserve buf(bufPos + dataLen - 1)
  97.   pdelta = (prevPhase / (sampleRate * seconds)) * -1
  98.   for i = 1 to dataLen
  99.     buf(bufPos) = cint(prevPhase * 32767)
  100.     prevPhase = prevPhase + pdelta
  101.     bufPos = bufPos + 1
  102.   next
  103.   genNeutral = dataLen
  104. end function
  105.  
  106. 'generate and replace with slience
  107. 'prevPhase will be reset to zero
  108. function genSilence(byref buf, byref bufPos, sampleRate, seconds, _
  109.   byref prevPhase)
  110.   dim dataLen, i
  111.   if not ((sampleRate >= 8000) and (sampleRate <= 48000)) then
  112.     sampleRate = 44100
  113.   end if
  114.   sampleRate = int(sampleRate)
  115.   if not ((seconds >= 0) and (seconds <= 1.79769313486231580779e308)) then
  116.     seconds = 1
  117.   end if
  118.   dataLen = sampleRate * seconds
  119.   redim preserve buf(bufPos + dataLen - 1)
  120.   for i = 1 to dataLen
  121.     buf(bufPos) = cint(0)
  122.     bufPos = bufPos + 1
  123.   next
  124.   prevPhase = 0
  125.   genSilence = dataLen
  126. end function
  127.  
  128. 'generate and replace with sine/square signal
  129. 'prevPhase is used for sine wave only
  130. function genWaveform(byref buf, byref bufPos, signalType, sampleRate, _
  131.   frequency, seconds, volume, byref prevPhase)
  132.   dim dataLen, i, data, t, tdelta, phase, pdelta, p
  133.   if not ((sampleRate >= 8000) and (sampleRate <= 48000)) then
  134.     sampleRate = 44100
  135.   end if
  136.   sampleRate = int(sampleRate)
  137.   if not ((frequency >= 0.001) and (frequency <= sampleRate)) then
  138.     frequency = 500
  139.   end if
  140.   if not ((seconds >= 0) and (seconds <= 1.79769313486231580779e308)) then
  141.     seconds = 1
  142.   end if
  143.   if not ((volume >= 0) and (volume <= 1)) then
  144.     volume = 0.5
  145.   end if
  146.   dataLen = sampleRate * seconds
  147.   redim preserve buf(bufPos + dataLen - 1)
  148.   t = 0
  149.   tdelta = 1 / sampleRate
  150.   data = atn(1) * 4 * frequency
  151.   if signalType = 1 then 'sine
  152.    phase = 0
  153.     if prevPhase > 0 then
  154.       pdelta = -prevPhase / (sampleRate * seconds / 1000)
  155.     elseif prevPhase < 0 then
  156.       pdelta = abs(prevPhase) / (sampleRate * seconds / 1000)
  157.     else
  158.       pdelta = 0
  159.     end if
  160.     for i = 1 to dataLen
  161.       phase = sin(data * t) * volume
  162.       p = phase * 32767
  163.       p = (phase + prevPhase) * 32767
  164.       if p > 32767 then
  165.         p = 32767
  166.       elseif p < -32768 then
  167.         p = -32768
  168.       end if
  169.       buf(bufPos) = cint(p)
  170.       t = t + tdelta
  171.       prevPhase = prevPhase + pdelta
  172.       if ((pdelta > 0) and (prevPhase > 0)) or _
  173.         ((pdelta < 0) and (prevPhase < 0)) then
  174.         prevPhase = 0
  175.         pdelta = 0
  176.       end if
  177.       bufPos = bufPos + 1
  178.     next
  179.     prevPhase = phase
  180.   else 'square
  181.    for i = 1 to dataLen
  182.       buf(bufPos) = cint(sgn(sin(data * t)) * volume * 32767)
  183.       t = t + tdelta
  184.       bufPos = bufPos + 1
  185.     next
  186.   end if
  187.   genWaveform = dataLen
  188. end function
  189.  
  190. 'get best matching wave type and its sampling rate.
  191. 'return array [type, sampleRate]
  192. function getBestMatchingWaveType(sampleRate)
  193.   dim t, a, i
  194.   t = 0
  195.   for i = 0 to ubound(sampleRates)
  196.     if sampleRates(i) = sampleRate then
  197.       t = i * 4 + 6
  198.       exit for
  199.     elseif (i + 1) < ubound(sampleRates) then
  200.       if (sampleRates(i) < sampleRate) and _
  201.         (sampleRate < sampleRates(i + 1)) then
  202.         t = sampleRate - sampleRates(i)
  203.         a = sampleRates(i + 1) - sampleRate
  204.         if a < t then
  205.           sampleRate = sampleRates(i + 1)
  206.           t = (i + 1) * 4 + 6
  207.         else
  208.           sampleRate = sampleRates(i)
  209.           t = i * 4 + 6
  210.         end if
  211.         exit for
  212.       end if
  213.     end if
  214.   next
  215.   if t = 0 then
  216.     sampleRate = 44100
  217.     t = 34
  218.   end if
  219.   getBestMatchingWaveType = array(t, sampleRate)
  220. end function
  221.  
  222. 'make wave memory stream.
  223. 'sampleRate rate is rounded to nearest or down
  224. function makeWaveformStream(sampleRate)
  225.   set makeWaveformStream = createobject("sapi.spmemorystream")
  226.   makeWaveformStream.format.type = getBestMatchingWaveType(sampleRate)(0)
  227.   makeWaveformStream.seek 0
  228. end function
  229.  
  230. function newbuf
  231.   dim ar
  232.   redim ar(-1)
  233.   newbuf = ar
  234. end function
  235.  
  236. 'render audio. returns array [stream, buffer]
  237. function render(code, sampleRate, byref signalType, byref speedBpm, _
  238.   byref measureLen, byref noteRestLen, byref defaultOctave, byref defaultVolume)
  239.   dim posSec, prevPhase, bufs, bufPoss, bufIdx, bufPos, _
  240.     xs, xp, stm, m, a, nt, ln, dr
  241.   sampleRate = getBestMatchingWaveType(sampleRate)(1)
  242.   if speedBpm <= 0 then
  243.     speedBpm = 1
  244.   elseif speedBpm > 9999 then
  245.     speedBpm = 9999
  246.   elseif not ((speedBpm >= 1) and (speedBpm <= 9999)) then
  247.     speedBpm = 100
  248.   end if
  249.   if measureLen <= 0 then
  250.     measureLen = 1
  251.   elseif measureLen > 999 then
  252.     measureLen = 999
  253.   elseif not ((measureLen >= 1) and (measureLen <= 999)) then
  254.     measureLen = 4
  255.   end if
  256.   if noteRestLen <= 0 then
  257.     noteRestLen = 1
  258.   elseif noteRestLen > 999 then
  259.     noteRestLen = 999
  260.   elseif not ((noteRestLen >= 1) and (noteRestLen <= 999)) then
  261.     noteRestLen = 4
  262.   end if
  263.   if defaultOctave < 0 then
  264.     defaultOctave = 0
  265.   elseif defaultOctave > 9 then
  266.     defaultOctave = 9
  267.   elseif not ((defaultOctave >= 0) and (defaultOctave <= 9)) then
  268.     defaultOctave = 4
  269.   end if
  270.   if defaultVolume < 0 then
  271.     defaultVolume = 0
  272.   elseif defaultVolume > 100 then
  273.     defaultVolume = 100
  274.   elseif not ((defaultVolume >= 0) and (defaultVolume <= 100)) then
  275.     defaultVolume = 50
  276.   end if
  277.  
  278.   posSec = 0 'pointer in seconds. internal
  279.  prevPhase = 0 'previous sine wave phase. internal
  280.  
  281.   redim bufs(0)
  282.   redim bufPoss(0)
  283.   bufIdx = 0
  284.   bufs(0) = newbuf
  285.   bufPoss(0) = 0
  286.   set xs = new regexp 'code splitter
  287.  xs.global = true
  288.   xs.pattern = "\S+"
  289.   set xp = new regexp 'code parser
  290.  xp.global = true
  291.   xp.ignorecase = true
  292.   xp.pattern = _
  293.     "^(?:" & _
  294.       "(?:" & _
  295.         "(c#?|d#?|e|f#?|g#?|a#?|b)([+-]?\d)?(\.\d{1,3})?" & _
  296.       ")" & _
  297.       "|" & _
  298.       "(\.\d{1,3})?" & _
  299.       "|" & _
  300.       "(" & _
  301.         "i\d|l\d{1,3}|m\d{1,4}|o\d|s\d{1,4}|" & _
  302.         "t\d{1,3}|v\d{1,3}|w\d|x\d{1,5}" & _
  303.       ")" & _
  304.     ")$"
  305.  
  306.   set stm = makeWaveformStream(sampleRate)
  307.   sampleRate = stm.format.getwaveformatex.samplespersec
  308.   genSilence bufs(0), bufPoss(0), sampleRate, 0.01, prevPhase
  309.   for each m in xs.execute(code)
  310.     set a = xp.execute(m)
  311.     if a.count = 0 then
  312.       wsh.echo "Invalid code at column " & (m.firstindex + 1) & "."
  313.       wsh.quit 1
  314.     end if
  315.     if a(0).submatches(4) <> "" then 'command/modifier
  316.      select case ucase(left(a(0).submatches(4), 1))
  317.         case "I" 'set signal type
  318.          signalType = cint(mid(a(0).submatches(4), 2))
  319.           if signalType <> 1 then signalType = 0
  320.         case "L" 'set default length
  321.          noteRestLen = cint(mid(a(0).submatches(4), 2))
  322.         case "M" 'set measure length
  323.          measureLen = cint(mid(a(0).submatches(4), 2))
  324.           if measureLen = 0 then measureLen = 1
  325.         case "O" 'set default octave
  326.          defaultOctave = cint(mid(a(0).submatches(4), 2))
  327.         case "S" 'set speed
  328.          speedBpm = cint(mid(a(0).submatches(4), 2))
  329.           if speedBpm = 0 then speedBpm = 1
  330.         case "T" 'track number
  331.          bufIdx = cint(mid(a(0).submatches(4), 2))
  332.           nt = ubound(bufs)
  333.           if bufIdx > nt then
  334.             redim preserve bufs(bufIdx)
  335.             redim preserve bufPoss(bufIdx)
  336.           end if
  337.           if isempty(bufs(bufIdx)) then
  338.             bufs(bufIdx) = newbuf
  339.             bufPoss(bufIdx) = 0
  340.           end if
  341.         case "V" 'set volume
  342.          defaultVolume = cint(mid(a(0).submatches(4), 2))
  343.           if defaultVolume > 100 then defaultVolume = 100
  344.         case "W" 'set signal type
  345.          signalType = cint(mid(a(0).submatches(4), 2))
  346.           if signalType <> 1 then signalType = 0
  347.         case "X" 'custom note
  348.          nt = cint(mid(a(0).submatches(4), 2))
  349.           if nt < 1 then
  350.             nt = 1
  351.           elseif nt > sampleRate then
  352.             nt = sampleRate
  353.           end if
  354.           dur = (60 / speedBpm) * (noteRestLen / measureLen)
  355.           genWaveform bufs(bufIdx), bufPoss(bufIdx), signalType, sampleRate, _
  356.             nt, dur, defaultVolume / 100, prevPhase
  357.           posSec = posSec + dur
  358.       end select
  359.     else 'note
  360.      nt = a(0).submatches(0) 'specify note
  361.      if nt <> "" then
  362.         nt = noteIndexes(ucase(nt))
  363.       else
  364.         nt = -1
  365.       end if
  366.       ln = a(0).submatches(1) 'update default octave
  367.      if ln <> "" then
  368.         if left(ln, 1) = "+" then
  369.           defaultOctave = defaultOctave + cint(right(ln, 1))
  370.         elseif left(ln, 1) = "-" then
  371.           defaultOctave = defaultOctave - cint(right(ln, 1))
  372.         else
  373.           defaultOctave = cint(ln)
  374.         end if
  375.       end if
  376.       ln = a(0).submatches(2) 'override note/rest length
  377.      if ln = "" then ln = a(0).submatches(3)
  378.       if (ln <> "") and (ln <> ".") then
  379.         ln = cint(mid(ln, 2))
  380.       else
  381.         ln = noteRestLen
  382.       end if
  383.       dr = (60 / speedBpm) * (ln / (measureLen / 4))
  384.       if nt >= 0 then 'generate note
  385.        genWaveform bufs(bufIdx), bufPoss(bufIdx), signalType, sampleRate, _
  386.           noteFreqs(defaultOctave)(nt), dr, defaultVolume / 100, prevPhase
  387.       elseif ln > 0 then 'generate rest
  388.        genSilence bufs(bufIdx), bufPoss(bufIdx), sampleRate, dr, prevPhase
  389.       end if
  390.       posSec = posSec + dr
  391.     end if
  392.   next
  393.  
  394.   dim buf()
  395.   xp = -1 'max data length
  396.  for a = 0 to ubound(bufs)
  397.     if ubound(bufs(a)) > xp then xp = ubound(bufs(a))
  398.   next
  399.   redim buf(xp)
  400.   for a = 0 to xp
  401.     nt = 0
  402.     for xs = 0 to ubound(bufs)
  403.       if ubound(bufs(xs)) >= a then
  404.         if not isempty(bufs(xs)(a)) then nt = nt + bufs(xs)(a)
  405.       end if
  406.     next
  407.     nt = cint(nt / (ubound(bufs) + 1))
  408.     if nt > 32767 then
  409.       buf(a) = 32767
  410.     elseif nt < -32768 then
  411.       buf(a) = -32768
  412.     else
  413.       buf(a) = nt
  414.     end if
  415.   next
  416.   bufPos = ubound(buf)
  417.   genNeutral buf, bufPos, sampleRate, 0.001, prevPhase
  418.   stm.write buf
  419.   stm.seek 0
  420.   render = array(stm, buf)
  421. end function
  422.  
  423. sub help
  424.   set f = fs.opentextfile(wsh.scriptfullname)
  425.   a = ""
  426.   do while not f.atendofstream
  427.     s = f.readline
  428.     if s <> "" then
  429.       a = a & mid(s, 2) & vbcrlf
  430.     else
  431.       exit do
  432.     end if
  433.   loop
  434.   wsh.echo left(a, len(a) - 2)
  435.   wsh.quit 1
  436. end sub
  437.  
  438. function readCode(f)
  439.   s = ""
  440.   do while not f.atendofstream
  441.     l = trim(f.readline)
  442.     if l <> "" then
  443.       if left(l, 1) <> ";" then s = s & l & " "
  444.     end if
  445.   loop
  446.   readCode = s
  447. end function
  448.  
  449. set fs = createobject("scripting.filesystemobject")
  450. fin = ""
  451. fout = ""
  452. play = true
  453. code = ""
  454. for each a in wsh.arguments.named
  455.   select case ucase(a)
  456.     case "I"
  457.       fin = wsh.arguments.named(a)
  458.     case "O"
  459.       fout = wsh.arguments.named(a)
  460.     case "R"
  461.       play = false
  462.     case else
  463.       help
  464.   end select
  465. next
  466. if fin <> "" then
  467.   set fi = fs.opentextfile(fin)
  468.   code = readCode(fi)
  469.   fi.close
  470. elseif wsh.arguments.unnamed.count > 0 then
  471.   code = wsh.arguments.unnamed(0)
  472. else
  473.   set fi = fs.getstandardstream(0)
  474.   if fi.atendofstream then
  475.     code = ""
  476.   else
  477.     code = readCode(fi)
  478.   end if
  479. end if
  480.  
  481. sampleRate = 44100
  482. signalType = 1 'sine
  483. speedBpm = 100
  484. measureLen = 16 '16/16
  485. noteRestLen = 4 '4/16 = 1/4
  486. defaultOctave = 4
  487. defaultVolume = 50
  488.  
  489. a = render(code, sampleRate, signalType, speedBpm, measureLen, noteRestLen, _
  490.   defaultOctave, defaultVolume)
  491.  
  492. if fout <> "" then
  493.   a(0).seek 0
  494.   a(0).read b, (ubound(a(1)) + 1) * 2
  495.   set fo = createobject("sapi.spfilestream")
  496.   set fo.format = a(0).format
  497.   fo.open fout, 3
  498.   fo.write b
  499.   fo.close
  500. end if
  501.  
  502. if play then
  503.   a(0).seek 0
  504.   set vc = createobject("sapi.spvoice")
  505.   vc.speakstream a(0)
  506. end if
  507.  
Add Comment
Please, Sign In to add comment