aboutsummaryrefslogtreecommitdiffhomepage
path: root/applause.lua
diff options
context:
space:
mode:
authorRobin Haberkorn <robin.haberkorn@googlemail.com>2023-09-04 03:01:13 +0300
committerRobin Haberkorn <robin.haberkorn@googlemail.com>2023-09-05 23:39:23 +0300
commit4fd919c5493d7e5100ed0ed944048c6ef8b59f50 (patch)
tree7457876aff69ddea14598bf68ed38822020cd879 /applause.lua
parent03ca9c8010702337a06c8b0ca9ca1e90c301d47e (diff)
downloadapplause2-4fd919c5493d7e5100ed0ed944048c6ef8b59f50.tar.gz
MIDI stuff has been moved into midi.lua
* common definitions are now in midi.h
Diffstat (limited to 'applause.lua')
-rw-r--r--applause.lua244
1 files changed, 1 insertions, 243 deletions
diff --git a/applause.lua b/applause.lua
index ff4bde5..675748e 100644
--- a/applause.lua
+++ b/applause.lua
@@ -90,11 +90,6 @@ enum applause_audio_state {
enum applause_audio_state applause_push_sample(int output_port_id,
double sample_double);
-// FIXME: Perhaps a struct would be easier to handle?
-typedef uint32_t applause_midi_sample;
-
-applause_midi_sample applause_pull_midi_sample(void);
-
// Useful in various situations
void free(void *ptr);
]]
@@ -296,23 +291,6 @@ function Stream:scale(v1, v2)
end
end
--- same as Stream:scale() but for values between [0, 127]
--- (ie. MIDI CC values)
--- FIXME: If Stream:CC() would output between [-1, 1], there would be no need
--- for Stream:ccscale().
-function Stream:ccscale(v1, v2)
- local lower = v2 and v1 or 0
- local upper = v2 or v1
-
- if type(lower) == "number" and type(upper) == "number" then
- return self:map(function(x)
- return (x/127)*(upper - lower) + lower
- end)
- else
- return self*((upper - lower)/127) + lower
- end
-end
-
function Stream:scan(fnc)
return ScanStream:new(self, fnc)
end
@@ -1731,227 +1709,6 @@ function DelayXStream:len()
end
--
--- MIDI Support
---
-
-MIDIStream = DeriveClass(Stream)
-
-function MIDIStream:gtick()
- return function()
- -- This is always cached since there is only one MIDI event queue
- -- and it must not be pulled more than once per tick.
- local sample = sampleCache[self]
- if not sample then
- sample = C.applause_pull_midi_sample()
- sampleCache[self] = sample
- end
- return sample
- end
-end
-
--- Last value of a specific control channel
-function Stream:CC(control, channel)
- channel = channel or 0
-
- assert(0 <= control and control <= 127,
- "MIDI control number out of range (0 <= x <= 127)")
- assert(0 <= channel and channel <= 15,
- "MIDI channel out of range (0 <= x <= 15)")
-
- local filter = bit.bor(0xB0, channel, bit.lshift(control, 8))
- local value = 0
- local band, rshift = bit.band, bit.rshift
-
- return self:map(function(sample)
- value = band(sample, 0xFFFF) == filter and
- tonumber(rshift(sample, 16)) or value
- return value
- end)
-end
-
--- Velocity of NOTE ON for a specific note on a channel
-function Stream:mvelocity(note, channel)
- -- `note` may be a note name like "A4"
- note = type(note) == "string" and ntom(note) or note
- channel = channel or 0
-
- assert(0 <= note and note <= 127,
- "MIDI note out of range (0 <= x <= 127)")
- assert(0 <= channel and channel <= 15,
- "MIDI channel out of range (0 <= x <= 15)")
-
- local on_filter = bit.bor(0x90, channel, bit.lshift(note, 8))
- local off_filter = bit.bor(0x80, channel, bit.lshift(note, 8))
- local value = 0
- local band, rshift = bit.band, bit.rshift
-
- return self:map(function(sample)
- value = band(sample, 0xFFFF) == on_filter and
- rshift(sample, 16) or
- band(sample, 0xFFFF) == off_filter and
- 0 or value
- return value
- end)
-end
-
---
--- MIDI primitives
---
-
-do
- local band = bit.band
- local floor, log = math.floor, math.log
-
- local note_names = {
- "C", "C#", "D", "D#", "E", "F",
- "F#", "G", "G#", "A", "A#", "B"
- }
-
- -- MIDI note number to name
- -- NOTE: mton() can handle the words as generated by MIDINoteStream
- function mton(note)
- note = band(note, 0xFF)
- local octave = floor(note / 12)-1
- return note_names[(note % 12)+1]..octave
- end
-
- function Stream:mton() return self:map(mton) end
-
- local ntom_offsets = {}
- for i, name in ipairs(note_names) do
- ntom_offsets[name] = i-1
- -- Saving the offsets for the lower-cased note names
- -- avoids a string.upper() call in ntom()
- ntom_offsets[name:lower()] = i-1
- end
-
- -- Note name to MIDI note number
- function ntom(name)
- local octave = name:byte(-1) - 48 + 1
- return octave*12 + ntom_offsets[name:sub(1, -2)]
- end
-
- function Stream:ntom() return self:map(ntom) end
-
- -- There are only 128 possible MIDI notes,
- -- so their frequencies can and should be cached.
- -- We do this once instead of on-demand, so the lookup
- -- table consists of consecutive numbers.
- local mtof_cache = table.new(128, 0)
- for note = 0, 127 do
- -- MIDI NOTE 69 corresponds to 440 Hz
- mtof_cache[note] = 440*math.pow(2, (note - 69)/12)
- end
-
- -- Convert from MIDI note to frequency
- -- NOTE: mtof() can handle the words as generated by MIDINoteStream
- function mtof(note)
- return mtof_cache[band(note, 0xFF)]
- end
-
- function Stream:mtof() return self:map(mtof) end
-
- -- Convert from frequency to closest MIDI note
- function ftom(freq)
- -- NOTE: math.log/2 is a LuaJIT extension
- return floor(12*log(freq/440, 2) + 0.5)+69
- end
-
- function Stream:ftom() return self:map(ftom) end
-end
-
--- Convert from MIDI name to frequency
-function ntof(name) return mtof(ntom(name)) end
-function Stream:ntof() return self:map(ntof) end
-
--- Convert from frequency to closest MIDI note name
-function fton(freq) return mton(ftom(freq)) end
-function Stream:fton() return self:map(fton) end
-
--- Tick an instrument only when an inputstream (note_stream),
--- gets ~= 0. When it changes back to 0 again, an "off"-stream
--- is triggered. This allows the construction of instruments with
--- Attack-Sustain and Decay phases based on real-time control signals.
--- The note values can be passed into the constructor by using functions
--- for the "on" and "off" streams.
--- Usually, the note stream will be a MIDIStream:mvelocity(), so the two
--- instrument streams can be based on the MIDI velocity (but don't have
--- to be if the velocity is not important).
-InstrumentStream = DeriveClass(MuxableStream)
-
-InstrumentStream.sig_last_stream = 1
-
-function InstrumentStream:muxableCtor(note_stream, on_stream, off_stream)
- note_stream = tostream(note_stream)
- local note_stream_cached
-
- if type(on_stream) == "function" then
- note_stream_cached = note_stream:cache()
- self.on_stream = on_stream(note_stream_cached)
- else
- self.on_stream = tostream(on_stream)
- end
- if type(off_stream) == "function" then
- note_stream_cached = note_stream_cached or note_stream:cache()
- self.off_stream = off_stream(note_stream_cached)
- else
- -- The "off" stream is optional
- self.off_stream = off_stream and tostream(off_stream)
- end
-
- -- `note_stream` is cached only when required
- self.note_stream = note_stream_cached or note_stream
-end
-
-function InstrumentStream:gtick()
- local note_tick = self.note_stream:gtick()
- local on_stream = self.on_stream
- local on_stream_inf = on_stream:len() == math.huge
- local off_stream = self.off_stream
- local on_tick
- local function off_tick() return 0 end
-
- return function()
- local note = note_tick()
- if not note then return end
-
- if on_tick == nil then -- no note
- if note == 0 then return off_tick() or 0 end
-
- -- FIXME: This is not strictly real-time safe
- on_tick = on_stream:gtick()
- return on_tick() or 0
- else -- note on
- if note ~= 0 then
- local sample = on_tick()
- if sample then return sample end
-
- -- on_stream must be finite, retrigger
- on_tick = on_stream:gtick()
- return on_tick() or 0
- elseif not on_stream_inf then
- -- don't cut off finite on_streams
- local sample = on_tick()
- if sample then return sample end
- end
-
- -- FIXME: This is not strictly real-time safe
- on_tick = nil
- if off_stream then off_tick = off_stream:gtick() end
- return off_tick() or 0
- end
- end
-end
-
-function InstrumentStream:len()
- return self.note_stream:len()
-end
-
-function Stream:instrument(on_stream, off_stream)
- return InstrumentStream:new(self, on_stream, off_stream)
-end
-
---
-- Primitives
--
@@ -2384,4 +2141,5 @@ Client.__gc = Client.kill
-- so they react to reload()
--
dofile "dssi.lua"
+dofile "midi.lua"
dofile "evdev.lua"