Beispiel #1
0
function ModulatorChunk (parentContext) {
  var context = Object.create(parentContext)
  var slots = NodeArray(context)
  context.slotLookup = lookup(slots, 'id')

  var obs = BaseChunk(context, {
    slots: slots,
    minimised: Property(false),
    color: Property([0, 0, 0])
  })

  obs._type = 'ModulatorChunk'

  obs.context = context
  context.chunk = obs

  var broadcastSchedule = null
  obs.onSchedule = Event(function (b) {
    broadcastSchedule = b
  })

  var currentTransform = null

  obs.slots.onUpdate(function () {
    if (currentTransform) {
      currentTransform.destroy()
      currentTransform = null
    }

    var transforms = [0]

    obs.slots.forEach(function (slot) {
      if (slot.onSchedule) {
        transforms.push({param: slot, transform: operation})
      }
    })

    currentTransform = Transform(context, transforms)
    currentTransform.onSchedule(broadcastSchedule)
  })

  obs.destroy = function () {
    if (currentTransform) {
      currentTransform.destroy()
      currentTransform = null
    }
  }

  return obs

  // scoped

  function operation (base, value) {
    return (parseFloat(base) || 0) + (parseFloat(value) || 0)
  }
}
Beispiel #2
0
function SlicerChunk (parentContext) {

  var context = Object.create(parentContext)
  var output = context.output = context.audio.createGain()
  context.output.connect(parentContext.output)

  var queueRefreshSlices = noargs(debounce(refreshSlices, 200))

  var slots = NodeArray(context)
  context.slotLookup = lookup(slots, 'id')

  var obs = BaseChunk(context, {
    sample: Sample(context),
    eq: EQ(context),

    sliceMode: Property('divide'),
    stretch: Property(false),
    tempo: Property(100),

    outputs: Property(['output']),
    routes: ExternalRouter(context, {output: '$default'}),
    volume: Property(1)
  }, {
    includedAllTriggers: true
  })

  var releaseMixerParams = applyMixerParams(obs)
  obs.overrideVolume = Property(1)

  var volume = computed([obs.volume, obs.overrideVolume], function (a, b) {
    return a * b
  })

  var watchApplied = obs.sample.resolvedBuffer(function (value) {
    if (value) {
      watchApplied()
      setImmediate(function () {
        obs.shape(queueRefreshSlices)
        obs.sample.resolvedBuffer(queueRefreshSlices)
        obs.sliceMode(queueRefreshSlices)
        obs.sample.mode(queueRefreshSlices)
        throttle(obs.sample.offset, 1000)(queueRefreshSlices)

        if (!obs.sample.slices()) { // ensure slices have been generated
          queueRefreshSlices()
        }
      })
    }
  })

   obs.sample.resolvedBuffer(function (value) {
     // without this everything breaks :( no idea why :(
   })

  var computedSlots = computedNextTick([
    obs.sample, obs.stretch, obs.tempo, obs.eq, volume, obs.sample.resolvedBuffer
  ], function (sample, stretch, tempo, eq, volume, buffer) {
    var result = (sample.slices || []).map(function (offset, i) {
      if (stretch && buffer) {

        var originalDuration = getOffsetDuration(buffer.duration, offset)
        var stretchedDuration = tempo / 60 * originalDuration

        return {
          node: 'slot',
          id: String(i),
          output: 'output',
          volume: volume,
          sources: [
            extend(sample, {
              node: 'source/granular',
              mode: 'oneshot',
              attack: 0.1,
              hold: 1,
              release: 0.1,
              duration: stretchedDuration,
              sync: true,
              offset: offset
            })
          ]
        }
      } else {
        return {
          node: 'slot',
          id: String(i),
          output: 'output',
          volume: volume,
          sources: [
            extend(sample, {
              node: 'source/sample',
              mode: 'oneshot',
              offset: offset
            })
          ]
        }
      }

    })

    result.unshift({
      node: 'slot',
      id: 'output',
      processors: [
        extend(eq, {node: 'processor/eq'})
      ]
    })

    return result
  })

  watch(computedSlots, slots.set)
  slots.onUpdate(obs.routes.refresh)

  obs.destroy = function(){
    releaseMixerParams()
    destroyAll(obs)
  }

  return obs

  // scoped
  function refreshSlices (cb) {
    var shape = obs.shape()
    var buffer = obs.sample.resolvedBuffer()
    var sliceMode = obs.sliceMode()
    var triggerMode = obs.sample.mode()
    var offset = obs.sample.offset()
    var count = shape[0] * shape[1]
    var playToEnd = triggerMode === 'full'
    if (sliceMode === 'peak' || sliceMode === 'transient') {
      if (buffer) {
        detectPeaks(buffer.getChannelData(0), count, offset, function (peaks) {
          obs.sample.slices.set(sliceOffsets(peaks, offset, playToEnd))
          cb && cb()
        })
      } else {
        cb && cb()
      }
    } else if (sliceMode === 'snap') {
      if (buffer) {
        gridSlicePeaks(buffer.getChannelData(0), count, offset, function (peaks) {
          obs.sample.slices.set(sliceOffsets(peaks, offset, playToEnd))
          cb && cb()
        })
      } else {
        cb && cb()
      }
    } else {
      obs.sample.slices.set(divideSlices(count, offset, playToEnd))
      cb && setImmediate(cb)
    }
  }

}
Beispiel #3
0
function SynthChunk (parentContext) {
  var context = Object.create(parentContext)
  context.output = context.audio.createGain()
  context.output.connect(parentContext.output)

  var slots = NodeArray(context)
  context.slotLookup = lookup(slots, 'id')

  var obs = BaseChunk(context, {
    osc1: Osc(context),
    osc2: Osc(context, {
      optional: true
    }),
    osc3: Osc(context, {
      optional: true,
      allowMultiply: true
    }),
    offset: Param(context, 0),
    amp: Param(context, 1),
    filter: Filter(context),
    eq: EQ(context),
    outputs: Property(['output']),
    volume: Property(1),
    routes: ExternalRouter(context, {output: '$default'})
  })

  var releaseMixerParams = applyMixerParams(obs)
  obs.overrideVolume = Property(1)

  var volume = computed([obs.volume, obs.overrideVolume], function (a, b) {
    return a * b
  })

  context.offset = obs.offset

  obs.amp.triggerable = true

  var scale = Property({
    offset: 0,
    notes: [0,2,4,5,7,9,11]
  })

  if (context.globalScale) {
    var releaseGlobalScale = watch(context.globalScale, scale.set)
  }

  var computedSlots = computed([
    obs.shape, obs.osc1, obs.osc2, obs.osc3, obs.filter, obs.amp, obs.eq, volume, scale
  ], function (shape, osc1, osc2, osc3, filter, amp, eq, volume, scale) {
    var length = shape[0] * shape[1]

    var result = [{
      node: 'slot',
      id: 'output',
      volume: volume,
      processors: [
        extend(eq, {node: 'processor/eq'}),
      ]
    }]

    for (var i = 0; i < length; i++) {
      var noteOffset = {
        node: 'modulator/scale',
        value: i,
        scale: scale
      }

      var sources = [
        extend(osc1, {node: 'source/oscillator' })
      ]

      var processors = [
        extend(filter, {node: 'processor/filter'}),
        { node: 'processor/gain',
          gain: amp
        }
      ]

      if (osc2.enabled) {
        sources.push(extend(osc2, {
          node: 'source/oscillator'
        }))
      }

      if (osc3.enabled) {
        if (osc3.multiply) {
          processors.unshift({
            node: 'processor/ring-modulator',
            carrier: osc3
          })
        } else {
          sources.push(extend(osc3,
            {node: 'source/oscillator'
          }))
        }
      }

      result.push({
        node: 'slot',
        id: String(i),
        output: 'output',
        noteOffset: noteOffset,
        sources: sources,
        processors: processors
      })
    }

    return result
  })

  computedSlots(slots.set)

  //throttleWatch(computedSlots, 50, function (value) {
  //  slots.set(value)
//
  //  // HACK: bump shape to trigger update of slot mapping
  //  obs.shape.set(obs.shape())
  //})

  slots.onUpdate(obs.routes.refresh)

  obs.destroy = function () {
    releaseMixerParams()
    destroyAll(obs)
    slots.destroy()
    releaseGlobalScale && releaseGlobalScale()
    releaseGlobalScale = null
  }
  
  return obs
}
Beispiel #4
0
function Setup(parentContext){

  var context = Object.create(parentContext)
  var audioContext = context.audio
  var refreshingParamCount = false

  var node = ObservStruct({
    controllers: NodeArray(context),
    chunks: NodeArray(context),
    selectedChunkId: Observ(),
    volume: Property(1),
    globalScale: Property({
      offset: 0,
      notes: [0,2,4,5,7,9,11]
    })
  })

  node.overrideVolume = Property(1)
  node.overrideLowPass = Property(0)
  node.overrideHighPass = Property(0)

  node._type = 'LoopDropSetup'

  context.setup = node
  context.globalScale = node.globalScale

  // main output
  context.output = audioContext.createGain()

  // mixer FX
  var outputLowPass = audioContext.createBiquadFilter()
  outputLowPass.Q.value = 0
  var outputHighPass = audioContext.createBiquadFilter()
  outputHighPass.Q.value = 0
  outputHighPass.type = 'highpass'

  context.output.connect(outputLowPass)
  outputLowPass.connect(outputHighPass)
  node.output = YankSilence(audioContext, outputHighPass)
  node.output.connect(parentContext.output)

  context.active = node.output.active

  watch(computed([node.volume, node.overrideVolume], (a, b) => a * b), function (value) {
    node.output.gain.value = value
  })

  watch(node.overrideLowPass, function (value) {
    outputLowPass.frequency.setTargetAtTime(interpolate(value, 20000, 20, 'exp'), audioContext.currentTime, 0.01)
  })

  watch(node.overrideHighPass, function (value) {
    outputHighPass.frequency.setTargetAtTime(interpolate(value, 20, 15000, 'exp'), audioContext.currentTime, 0.01)
  })

  node.onTrigger = Event(function (b) {
    context.triggerEvent = b
  })

  node.onTrigger(function (event) {
    if (event.id) {
      node.output.trigger()
      var split = event.id.split('/')
      var chunk = context.chunkLookup.get(split[0])
      var slotId = split[1]
      if (chunk) {
        if (event.event === 'start' && event.time >= context.audio.currentTime - 0.001) {
          chunk.triggerOn(slotId, event.time)
        } else if (event.event === 'stop') {
          chunk.triggerOff(slotId, event.time)
        }
      }
    }
  })

  node.chunks.resolveAvailable = function(id){
    var base = id
    var lookup = context.chunkLookup()
    var incr = 0

    while (lookup[id]){
      incr += 1
      id = base + ' ' + (incr + 1)
    }

    return id
  }

  // deprecated: use chunks.resolveAvailable
  node.resolveAvailableChunk = node.chunks.resolveAvailable

  node.destroy = function(){
    destroyAll(node)
    context.paramLookup.destroy()
  }

  // maps and lookup
  node.controllers.resolved = map(node.controllers, resolve)
  node.chunks.resolved = map(node.chunks, resolve)
  node.chunks.lookup = lookup(node.chunks, function(x){
    var descriptor = get(x)
    return descriptor && descriptor.id || undefined
  })

  // enforce controller types
  node.controllers.onUpdate(function (update) {
    update.slice(2).forEach(function (controller) {
      if (controller.port) {
        assignAvailablePort(controller)
      }
    })
  })

  context.chunkLookup = lookup(node.chunks, function(x){
    if (x){
      var data = x.resolved ? x.resolved() : x()
      return data && data.id || undefined
    }
  }, resolve, resolveInner)


  // extend param lookup
  var lookups = []
  if (context.paramLookup) {
    lookups.push(context.paramLookup)
  }

  lookups.push(
    lookup(node.chunks, function(x){
      if (x && x.onSchedule){
        return x.id()
      }
    }, resolve, resolveInner)
  )

  context.paramLookup = merge(lookups)
  node.context = context

  node.resolved = ObservStruct({
    selectedChunkId: node.selectedChunkId,
    controllers: node.controllers.resolved,
    chunks: node.chunks.resolved,
    paramCount: Observ(0)
  })

  context.paramLookup(refreshParamCount)

  node.grabInput = function(){
    var length = node.controllers.getLength()
    for (var i=0;i<length;i++){
      var controller = node.controllers.get(i)
      if (controller.grabInput){
        controller.grabInput()
      }
    }

    // now focus the selected chunk
    if (node.selectedChunkId){
      var chunkId = node.selectedChunkId()
      for (var i=0;i<length;i++){
        var controller = node.controllers.get(i)
        var chunkPositions = controller().chunkPositions || {}
        if (controller.grabInput && chunkPositions[chunkId]){
          controller.grabInput()
        }
      }
    }
  }

  node.updateChunkReferences = function (oldId, newId) {
    node.controllers.forEach(function (controller) {
      if (controller.chunkPositions && controller.chunkPositions()) {
        var value = controller.chunkPositions()[oldId]
        if (value && controller.chunkPositions.put) {
          controller.chunkPositions.delete(oldId)
          if (newId) {
            controller.chunkPositions.put(newId, value)
          }
        }
      }

      if (controller.chunkIds && controller.chunkIds()) {
        controller.chunkIds.set(controller.chunkIds().map(x => x === oldId ? newId : x))
      }
    })

    updateParamReferences(node.chunks, oldId, newId)

    // check for routes matching chunk id
    node.chunks.forEach(function (chunk) {
      updateRouteReferences(chunk, oldId, newId)
    })

    if (node.selectedChunkId() === oldId){
      node.selectedChunkId.set(newId)
    }
  }

  return node

  // scoped

  function refreshParamCount () {
    if (!refreshingParamCount) {
      refreshingParamCount = true
      process.nextTick(refreshParamCountNow)
    }
  }

  function refreshParamCountNow () {
    refreshingParamCount = false
    var count = Object.keys(context.paramLookup()).length
    if (count !== node.resolved.paramCount()) {
      node.resolved.paramCount.set(count)
    }
  }
}
function ModulatorChunk(parentContext){

  var context = Object.create(parentContext)

  var obs = ObservStruct({
    id: Observ(),
    slots: NodeArray(context),
    minimised: Property(false),
    shape: Property([1,1]),
    color: Property([0,0,0]),
    flags: Property([])
  })

  obs._type = 'ModulatorChunk'

  obs.context = context
  context.chunk = obs

  context.slotLookup = lookup(obs.slots, 'id')

  var broadcastSchedule = null
  obs.onSchedule = Event(function(b){
    broadcastSchedule = b
  })

  var currentTransform = null

  obs.slots.onUpdate(function(){

    if (currentTransform){
      currentTransform.destroy()
      currentTransform = null
    }

    var transforms = [0]

    obs.slots.forEach(function(slot){
      if (slot.onSchedule){
        transforms.push({param: slot, transform: operation})
      }
    })

    currentTransform = Transform(context, transforms)
    currentTransform.onSchedule(broadcastSchedule)

  })

  obs.triggers = computed([obs.id, obs.shape], function(id, shape){
    var length = shape[0] * shape[1]
    var result = []
    for (var i=0;i<length;i++){
      result.push(String(i))
    }
    return result
  })

  obs.grid = computed([obs.triggers, obs.shape], ArrayGrid)

  obs.triggerOn = function(id, at){
    var slot = context.slotLookup.get(id)
    if (slot){
      slot.triggerOn(at)
    }
  }

  obs.triggerOff = function(id, at){
    var slot = context.slotLookup.get(id)
    if (slot){
      slot.triggerOff(at)
    }
  }

  obs.destroy = function(){
    if (currentTransform){
      currentTransform.destroy()
      currentTransform = null
    }
  }

  obs.resolvedGrid = computed([obs.triggers, obs.shape], function(triggers, shape){
    return ArrayGrid(triggers.map(getGlobalId), shape)
  })

  return obs

  // scoped

  function operation(base, value){
    return (parseFloat(base)||0) + (parseFloat(value)||0)
  }

  function getGlobalId(id){
    if (id){
      return obs.id() + '/' + id
    }
  }

}