{"id":3053,"date":"2021-08-25T13:10:41","date_gmt":"2021-08-25T13:10:41","guid":{"rendered":"https:\/\/rengga.dev\/blog\/?p=3053"},"modified":"2023-02-28T00:44:11","modified_gmt":"2023-02-28T00:44:11","slug":"js-tutorial-math-random-computer-generated-music","status":"publish","type":"post","link":"https:\/\/rengga.dev\/blog\/js-tutorial-math-random-computer-generated-music\/","title":{"rendered":"JS Tutorial &#8211; Math.random() Computer-Generated Music"},"content":{"rendered":"<p><span style=\"color: #ef3207;\"><a style=\"color: #ef3207;\" href=\"https:\/\/rengga.dev\/\" target=\"_blank\" rel=\"noopener\"><strong>Rengga Dev<\/strong><\/a> <\/span>&#8211; <code>Math.random()<\/code>\u00a0is an API in JavaScript. It is a function that gives you a random number. The number returned will be between 0 (inclusive, as in, it\u2019s possible for an actual 0 to be returned) and 1 (exclusive, as in, it\u2019s not possible for an actual 1 to be returned).<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"js\"># generate a package of chances and values based on an object containing multiple possible zero values\r\nChancePkg = (vals) -&gt;\r\n  # generating vals and their chances of occuring\r\n  new_vals = []\r\n  # get total number of vals    \r\n  vals_count = 0\r\n  for value of vals\r\n    if vals.hasOwnProperty(value) &amp;&amp; vals[value] &gt; 0\r\n      vals_count += vals[value]\r\n      new_vals.push [value, vals[value]]\r\n  # go through new vals and generate chance for each value\r\n  new_vals_chances = []  \r\n  total = 0  \r\n  for val, i in new_vals\r\n    # relative amount for random selection    \r\n    amount = total + ( (val[1] \/ vals_count ) * 100000)\r\n    # push amount into chances array    \r\n    new_vals_chances.push amount    \r\n    # set value to be the value, no longer an array of data    \r\n    new_vals[i] = parseInt(val[0])\r\n    # increase base total    \r\n    total = amount  \r\n  # return package which has array for both non-zero vals and their corrosponding chance\r\n  return {chances: new_vals_chances, vals: new_vals}\r\n\r\n\r\n\r\n# get the sound to silence ratio at the smallest scale\r\ngetSoundRatio = (measures, beats, values) -&gt;\r\n  # generating vals and their chances of occuring\r\n  new_values = []\r\n  # get total number of vals    \r\n  values_count = 0\r\n  for value of values\r\n    if values.hasOwnProperty(value) &amp;&amp; values[value] &gt; 0\r\n      values_count += values[value]\r\n      new_values.push [value, values[value]]  \r\n  # resolution is the smallest sized note  \r\n  resolution = (beats * (new_values[new_values.length - 1][1] \/ 4))\r\n  # possible is how many resolution sized notes are in the piece  \r\n  possible = measures * (beats * (resolution \/ 4))\r\n  # usage to be used in the loop  \r\n  usage = 0  \r\n  for value in new_values\r\n    # how many resolution values are in a single value? (ie. how many sixteenth notes are in a quarter note)    \r\n    res_value = (1 \/ value[0]) * resolution\r\n    # use resolution value * occurances    \r\n    usage += res_value * value[1]    \r\n  return usage \/ possible\r\n\r\n\r\n\r\n# generate clef values based on data\r\nPiece = (piece) -&gt;\r\n  {measures, beats, treble_clef, bass_clef, composition} = piece\r\n  this.treble_clef = {\r\n    # sound ratio is the presence of sound divided by the presenece of silence in the analyzed piece  \r\n    sound_ratio: getSoundRatio(measures, beats, treble_clef.values)\r\n    # value package has array for both non-zero values and their corrosponding chance\r\n    values_pkg: new ChancePkg(treble_clef.values)\r\n    # interval package has array for both non-zero intervals and their corrosponding chance\r\n    intervals_pkg: new ChancePkg(treble_clef.intervals)\r\n    # octaves package has array for both non-zero octaves and their corrosponding chance\r\n    octaves_pkg: new ChancePkg(treble_clef.octaves)\r\n    # chords package has array for both non-zero chords and their corrosponding chance\r\n    chords_pkg: new ChancePkg(treble_clef.chords)\r\n    # setting the base octave\r\n    base_octave: 5\r\n    # tone\r\n    wave: 'sine'\r\n    # gain\r\n    gain: 0.3\r\n  }\r\n  this.bass_clef = {\r\n    # sound ratio is the presence of sound divided by the presenece of silence in the analyzed piece  \r\n    sound_ratio: getSoundRatio(measures, beats, bass_clef.values)\r\n    # value package has array for both non-zero values and their corrosponding chance\r\n    values_pkg: new ChancePkg(bass_clef.values)\r\n    # interval package has array for both non-zero intervals and their corrosponding chance\r\n    intervals_pkg: new ChancePkg(bass_clef.intervals)\r\n    # octaves package has array for both non-zero octaves and their corrosponding chance\r\n    octaves_pkg: new ChancePkg(bass_clef.octaves)\r\n    # chords package has array for both non-zero chords and their corrosponding chance\r\n    chords_pkg: new ChancePkg(bass_clef.chords)\r\n    # setting the base octave\r\n    base_octave: 3\r\n    # tone\r\n    wave: 'sine'\r\n    # gain\r\n    gain: 0.3\r\n  }\r\n  this.composition = composition\r\n  return\r\n  \r\n\r\n  \r\n# initiate audio context\r\naudio_context = undefined\r\n(init = (g) -&gt;\r\n  try\r\n    # \"crossbrowser\" audio context.\r\n    audio_context = new (g.AudioContext or g.webkitAudioContext)\r\n  catch e\r\n    console.log \"No web audio oscillator support in this browser\"\r\n  return\r\n) window\r\n\r\n\r\n\r\n# oscillator prototype\r\nOscillator = (tone) -&gt;\r\n  max_gain = tone.gain\r\n  this.tone = tone\r\n  this.play = () -&gt;\r\n    # capturing current time for play start and stop\r\n    current_time = audio_context.currentTime\r\n    # create oscillator    \r\n    o = audio_context.createOscillator()\r\n    # create gain    \r\n    gn = audio_context.createGain()    \r\n    # set waveform    \r\n    o.type = this.tone.wave\r\n    # set frequency    \r\n    if this.tone.frequency\r\n      o.frequency.value = this.tone.frequency\r\n    # connect oscillator to gain    \r\n    o.connect gn\r\n    # connect gain to output\r\n    gn.connect audio_context.destination\r\n    # set gain amount\r\n    gn.gain.value = (max_gain \/ this.tone.vol) \/ 1\r\n    # play it    \r\n    o.start(current_time)\r\n    # stop after sustain    \r\n    o.stop(current_time + this.tone.sustain)\r\n  return this\r\n\r\n\r\n\r\n# note frequencies array of octave arrays that start on c (our root note)\r\nfreqs = [\r\n  [16.351, 17.324, 18.354, 19.445, 20.601, 21.827, 23.124, 24.499, 25.956, 27.5, 29.135, 30.868]\r\n  [32.703, 34.648, 36.708, 38.891, 41.203, 43.654, 46.249, 48.999, 51.913, 55, 58.27, 61.735]\r\n  [65.406, 69.296, 73.416, 77.782, 82.407, 87.307, 92.499, 97.999, 103.826, 110, 116.541, 123.471]\r\n  [130.813, 138.591, 146.832, 155.563, 164.814, 174.614, 184.997, 195.998, 207.652, 220, 233.082, 246.942]\r\n  [261.626, 277.183, 293.665, 311.127, 329.628, 349.228, 369.994, 391.995, 415.305, 440, 466.164, 493.883]\r\n  [523.251, 554.365, 587.33, 622.254, 659.255, 698.456, 739.989, 783.991, 830.609, 880, 932.328, 987.767]\r\n  [1046.502, 1108.731, 1174.659, 1244.508, 1318.51, 1396.913, 1479.978, 1567.982, 1661.219, 1760, 1864.655, 1975.533]\r\n  [2093.005, 2217.461, 2349.318, 2489.016, 2637.021, 2793.826, 2959.955, 3135.964, 3322.438, 3520, 3729.31, 3951.066]\r\n  [4186.009, 4434.922, 4698.636, 4978.032, 5274.042, 5587.652, 5919.91, 6271.928, 6644.876, 7040, 7458.62, 7902.132]\r\n  [8372.018, 8869.844, 9397.272, 9956.064, 10548.084, 11175.304, 11839.82, 12543.856, 13289.752, 14080, 14917.24, 15804.264]\r\n]\r\n\r\n\r\n\r\n# get random val from chance package\r\nrandomVal = (chance_pkg) -&gt;\r\n  random = Math.random() * 100000\r\n  for chance, i in chance_pkg.chances\r\n    return chance_pkg.vals[i] if random &lt; chance\r\n  return\r\n\r\n# get sequence function\r\ngetSequence = (clef, composition) -&gt;\r\n  # get the duration in smallest resolution amount\r\n  duration = composition.measures * (composition.beats * (composition.resolution \/ 4))  \r\n  # note sequence\r\n  sequence = []\r\n  # while there is still duration\r\n  while duration &gt; 0\r\n    # random hit\r\n    random = Math.random()\r\n    # if a hit    \r\n    if random &lt; clef.sound_ratio \r\n      # random chord note count\r\n      chord = randomVal(clef.chords_pkg)\r\n      # random length for the chord      \r\n      value = randomVal(clef.values_pkg)\r\n      # if there isnt enough space\r\n      if ((1 \/ value) \/ (1 \/ composition.resolution)) &gt;= duration\r\n        # make it the length of remaining   \r\n        value = ((1 \/ duration) \/ (1 \/ composition.resolution))\r\n      # the new chord\r\n      new_chord = {length: (1 \/ value), notes: []}\r\n      # for each note in the chord      \r\n      for note in [1..chord]\r\n        # get a random interval\r\n        interval = randomVal(clef.intervals_pkg)\r\n        # get the random octave        \r\n        octave = randomVal(clef.octaves_pkg)\r\n        # make intervale relative to key\r\n        interval += composition.root\r\n        # if key pushes interval into new octave\r\n        if interval &gt; 12\r\n          interval -= 12\r\n        # make the octave relative to the clef's octave\r\n        new_octave = clef.base_octave + ((2 - octave) * -1)\r\n        # the frequency of the note\r\n        note = freqs[new_octave - 1][interval]\r\n        # if note doesnt already exist in chord\r\n        if new_chord.notes.indexOf(note) == -1\r\n          # push the frequency into the chord's notes        \r\n          new_chord.notes.push {freq: note, int: interval, octave: octave}\r\n        else\r\n          # duplicate note in chord, ignoring it for now.\r\n          console.log 'duplicate note in chord, ignoring it for now.'\r\n      # push the chord into the sequence        \r\n      sequence.push new_chord\r\n      # get values resolution-relative value        \r\n      res_value = Math.floor((1 \/ value) \/ (1 \/ composition.resolution))\r\n      # if we need to add sustain notes                   \r\n      if res_value &gt; 1\r\n        # add blank values      \r\n        for blank in [1..res_value - 1]      \r\n          sequence.push 'sus'\r\n      # subtract from the duration      \r\n      duration -= res_value\r\n\r\n    else\r\n      # it was a miss, add a zero to the sequence\r\n      sequence.push 0\r\n      # subtract tick from duration      \r\n      duration--\r\n  return sequence\r\n\r\n\r\n\r\nNote = (tone) -&gt;\r\n  this.osc = () -&gt;\r\n    return new Oscillator(tone)\r\n  return\r\n\r\n\r\n###\r\n# data (to be derived from an analyzed piece of music)\r\n###\r\n\r\npiece = {\r\n  # piece data (auld lang syne)\r\n  measures: 16 # measures in analyzed piece\r\n  beats:    4  # beats per measure in analyzed piece\r\n  # all our analysis will be relative to each clef\r\n  treble_clef: {  \r\n    # note values are the duration of the note \/ chord. \r\n    # this is a map of how many times each value appears in the analyzed data\r\n    values: {\r\n      1:   0  # whole\r\n      1.5: 4  # dotted half\r\n      2:   5  # half\r\n      3:   12 # dotted quarter\r\n      4:   28 # quarter\r\n      6:   0  # dotted eighth\r\n      8:   12 # eighth\r\n      12:  0  # dotted sixteenth\r\n      16:  0  # sixteenth\r\n      32:  0  # thirty-second\r\n    }\r\n    # relative to the key, the intervals are steps between notes and the root (no octaves)\r\n    # this includes all single notes and instances of a note in a chord\r\n    # this is a map of how many times each interval appears in the analyzed data\r\n    intervals: {\r\n      0:  32 # root \/ perfect unison (F)\r\n      1:  0  # minor second (F#)\r\n      2:  9  # major second (G)\r\n      3:  0  # minor third (G#)\r\n      4:  13 # major third (A)\r\n      5:  0  # perfect fourth (A#)\r\n      6:  6  # tritone (B)\r\n      7:  13 # perfect fifth (C)\r\n      8:  0  # minor sixth (C#)\r\n      9:  15 # major sixth (D)\r\n      10: 0  # minor seventh (D#)\r\n      11: 12 # major seventh (E)\r\n    }\r\n    # octaves are how many times a note appears in each octave (relative to the key)\r\n    octaves: {\r\n      1: 37 # clef - 1\r\n      2: 67 # clef\r\n      3: 1  # clef + 1\r\n    }\r\n    # instead of using proper chords (dyad, triad, 7th, 9th, and 11th), we only analyze how many notes are in the chord\r\n    # this is a map of how many times each chord size appears in the analyzed data\r\n    chords: {\r\n      1: 9  # 1 note\r\n      2: 47 # 2 notes\r\n      3: 0  # 3 notes\r\n      4: 0  # 4 notes\r\n      5: 0  # 5 notes\r\n    }\r\n  }\r\n  bass_clef: {  \r\n    # note values are the duration of the note \/ chord. \r\n    # this is a map of how many times each value appears in the analyzed data\r\n    values: {\r\n      1:   0  # whole\r\n      1.5: 4  # dotted half\r\n      2:   5  # half\r\n      3:   12 # dotted quarter\r\n      4:   28 # quarter\r\n      6:   0  # dotted eighth\r\n      8:   12 # eighth\r\n      12:  0  # dotted sixteenth\r\n      16:  0  # sixteenth\r\n      32:  0  # thirty-second\r\n    }\r\n    # relative to the key, the intervals are steps between notes and the root (no octaves)\r\n    # this includes all single notes and instances of a note in a chord\r\n    # this is a map of how many times each interval appears in the analyzed data\r\n    intervals: {\r\n      0:  25 # root \/ perfect unison (F)\r\n      1:  0  # minor second (F#)\r\n      2:  1  # major second (G)\r\n      3:  0  # minor third (G#)\r\n      4:  17 # major third (A)\r\n      5:  2  # perfect fourth (A#)\r\n      6:  0  # tritone (B)\r\n      7:  38 # perfect fifth (C)\r\n      8:  1  # minor sixth (C#)\r\n      9:  4  # major sixth (D)\r\n      10: 0  # minor seventh (D#)\r\n      11: 2  # major seventh (E)\r\n    }\r\n    # octaves are how many times a note appears in each octave (relative to the key)\r\n    octaves: {\r\n      1: 2  # clef - 1\r\n      2: 53 # clef\r\n      3: 50 # clef + 1\r\n    }\r\n    # instead of using proper chords (dyad, triad, 7th, 9th, and 11th), we only analyze how many notes are in the chord\r\n    # this is a map of how many times each chord size appears in the analyzed data\r\n    chords: {\r\n      1: 9  # 1 note\r\n      2: 46 # 2 notes\r\n      3: 0  # 3 notes\r\n      4: 0  # 4 notes\r\n      5: 0  # 5 notes\r\n    }\r\n\r\n  }\r\n  # defining the desired output composition data\r\n  composition: {\r\n    measures:   32  # bars to generate\r\n    beats:      4   # beats per measure\r\n    tempo:      120 # tempo\r\n    resolution: 16  # resolution scale of piece  \r\n    root:       5   # root of key (0-11), 0 is 'C'\r\n  }\r\n}\r\n\r\n\r\n# lets analyze our piece data!\r\np = new Piece(piece)\r\n\r\n# sequence stores\r\ntrebleSequence = undefined\r\nbassSequence = undefined\r\n# get sequences\r\ngetSequences = () -&gt;\r\n  # creating our treble clef\r\n  trebleSequence = getSequence(p.treble_clef, p.composition)\r\n  bassSequence = getSequence(p.bass_clef, p.composition)\r\n  draw_sequences()\r\n\r\n  \r\n  \r\ngetSequenceHtml = (name, composition, sequence) -&gt;\r\n  $seq_html = $('&lt;div class=\"' + name + ' clef\"&gt;&lt;\/div&gt;')\r\n  beats = composition.measures * (composition.beats * (composition.resolution \/ 4))  \r\n  width_increment = 1 \/ beats * 100\r\n  left = 0  \r\n  for chord in sequence\r\n    if typeof chord == \"object\" \r\n      width = width_increment * (chord.length \/ (1 \/ composition.resolution))      \r\n      width = width_increment\r\n      classname = 'chord value-' + Math.round( (1 \/ chord.length) * 100) \/ 100\r\n      classname = classname.replace('.', '-')\r\n      notes = ''  \r\n      for note in chord.notes\r\n        out_of_36 = 36 - (note.int + ((note.octave - 1) * 12))\r\n        notes += '&lt;span class=\"note note-' + out_of_36 + '\"&gt;&lt;\/span&gt;'\r\n    else if chord == 'sus'  \r\n      classname = 'sus'\r\n      width = width_increment\r\n      notes = ''  \r\n    else\r\n      width = width_increment      \r\n      classname = 'blank'\r\n      notes = ''\r\n    $seq_html.append '&lt;span class=\"beat ' + classname + '\" style=\"width: ' + width + '%; left: ' + left + '%\"&gt;' + notes + '&lt;\/span&gt;'\r\n    left += width\r\n  return $seq_html\r\n\r\n\r\n\r\ndraw_sequences = () -&gt;\r\n  composition = p.composition\r\n\r\n  $staffs = $('&lt;div class=\"staffs-wrapper\"&gt;&lt;\/div&gt;')\r\n  $staffs.append getSequenceHtml('treble', composition, trebleSequence)\r\n  $staffs.append getSequenceHtml('bass', composition, bassSequence)\r\n  $('#staff').html $staffs\r\n  return\r\n    \r\n\r\n\r\nperformance_interval = undefined\r\n\r\n# play sequences\r\nplaySequences = () -&gt;\r\n  composition = p.composition\r\n  sequences = [trebleSequence, bassSequence]\r\n  waves = [p.treble_clef.wave, p.bass_clef.wave]\r\n  gains = [p.treble_clef.gain, p.bass_clef.gain]\r\n  # total beat count\r\n  beats = composition.measures * (composition.beats * (composition.resolution \/ 4))\r\n  # css width of beat  \r\n  beat_width = 100 \/ beats\r\n  # relative index  \r\n  index = 0\r\n  # tempo to ms  \r\n  tempo_time = 60000 \/ composition.tempo        \r\n  # single beat instance\r\n  next_beat = () -&gt;\r\n    for sequence, i in sequences\r\n      chord = sequence[index]\r\n      # if beat in any rhythm array has value   \r\n      if typeof chord == \"object\"\r\n        # beats per second\r\n        bps = composition.tempo \/ 60\r\n        # how much of a beat is the length        \r\n        beat_count = chord.length \/ 0.25\r\n        # sustain of the note in seconds\r\n        chord_length_secs = beat_count * bps \/ 2\r\n        sustain = (chord_length_secs \/ bps) - 0.1\r\n        for note in chord.notes\r\n          # new note\r\n          n = new Note({frequency: note.freq, sustain: sustain, wave: waves[i], gain: gains[i], vol: chord.notes.length})\r\n          # new oscillator\r\n          o = n.osc()\r\n          # play oscillator        \r\n          o.play()\r\n        \r\n        if i == 0 then clef = 'treble' else clef = 'bass'\r\n          \r\n      if typeof chord == \"object\"         \r\n        $('.' + clef + ' .beat.active').removeClass 'active'\r\n        $('.' + clef + ' .beat.go').removeClass clef + '-go'\r\n        $('.' + clef + ' .beat:nth-child(' + (index + 1) + ')').addClass clef + '-go'\r\n      else if chord != 'sus'     \r\n        $('.' + clef + ' .beat.active').removeClass 'active'\r\n        $('.' + clef + ' .beat.go').removeClass clef + '-go'\r\n        \r\n      $('.beat:nth-child(' + (index + 1) + ')').addClass 'active'\r\n        \r\n    # update index    \r\n    index = (index + 1) % beats\r\n  # first call of next beat\r\n  next_beat()\r\n  # ms to relative speed (based on resolution)  \r\n  time = tempo_time \/ (composition.resolution \/ 4)\r\n  # set interval for next beat to occur at approriate time\r\n  performance_interval = window.setInterval(next_beat, time)\r\n\r\n# stop button\r\nstopSequences = () -&gt;\r\n  window.clearInterval(performance_interval)\r\n\r\n# get sequences\r\ngetSequences()\r\n\r\nplaying = false\r\nplay_handler = (p) -&gt;\r\n  if p == true  \r\n    playSequences()\r\n  else    \r\n    stopSequences()\r\n  playing = p   \r\n\r\n$('#play').click () -&gt;\r\n  $(this).toggleClass 'playing'\r\n  $('body').toggleClass 'playing'\r\n  $('#new').toggleClass 'inactive'\r\n  play_handler(!playing)\r\n  \r\n$('#new').click () -&gt;\r\n  getSequences()<\/pre>\n<p>&nbsp;<\/p>\n<p><iframe style=\"width: 100%;\" title=\"Random Music Sequencer\" src=\"https:\/\/codepen.io\/jakealbaugh\/embed\/zxoOjG?default-tab=result&amp;theme-id=dark\" height=\"700\" frameborder=\"no\" scrolling=\"no\" allowfullscreen=\"allowfullscreen\"><br \/>\nSee the Pen <a href=\"https:\/\/codepen.io\/jakealbaugh\/pen\/zxoOjG\"><br \/>\nRandom Music Sequencer<\/a> by Jake Albaugh (<a href=\"https:\/\/codepen.io\/jakealbaugh\">@jakealbaugh<\/a>)<br \/>\non <a href=\"https:\/\/codepen.io\">CodePen<\/a>.<br \/>\n<\/iframe><br \/>\nThis program takes the traditional melody of\u00a0<a href=\"https:\/\/en.wikipedia.org\/wiki\/Auld_Lang_Syne\" rel=\"noopener\">\u201cAuld Lang Syne\u201d<\/a>\u00a0and plays random notes from it in piano. A change package is created from the count data and a random number is generated to select a value. The octave is also randomly selected.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Rengga Dev &#8211; Math.random()\u00a0is an API in JavaScript. It is a function <a class=\"read-more\" href=\"https:\/\/rengga.dev\/blog\/js-tutorial-math-random-computer-generated-music\/\" title=\"JS Tutorial &#8211; Math.random() Computer-Generated Music\" itemprop=\"url\"><\/a><\/p>\n","protected":false},"author":1,"featured_media":3796,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[20,12],"tags":[276,264,263,274,103,227],"newstopic":[],"class_list":{"0":"post-3053","1":"post","2":"type-post","3":"status-publish","4":"format-standard","5":"has-post-thumbnail","7":"category-javascript","8":"category-web-development","9":"tag-computer-generated-music","10":"tag-javascript-tutorial","11":"tag-js-tutorial","12":"tag-math-random","13":"tag-web-design","14":"tag-web-designer"},"_links":{"self":[{"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/posts\/3053","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/comments?post=3053"}],"version-history":[{"count":1,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/posts\/3053\/revisions"}],"predecessor-version":[{"id":3055,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/posts\/3053\/revisions\/3055"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/media\/3796"}],"wp:attachment":[{"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/media?parent=3053"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/categories?post=3053"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/tags?post=3053"},{"taxonomy":"newstopic","embeddable":true,"href":"https:\/\/rengga.dev\/blog\/wp-json\/wp\/v2\/newstopic?post=3053"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}