* calc/calc-units.el (math-midi-round, math-freqp, math-midip)

(math-spnp, math-spn-to-midi, math-midi-to-spn, math-freq-to-spn)
  (math-midi-to-freq, math-spn-to-freq, calcFunc-spn, calcFunc-midi)
  (calcFunc-freq, calc-freq, calc-midi, calc-spn): New functions.
  (math-notes): New variable.

* calc/calc.el (calc-note-threshold): New variable.

* calc/calc-ext.el (calc-init-extensions): Add keybindings for
  calc-spn, calc-midi, calc-freq.  Add autoloads for calcFunc-spn,
  calcFunc-midi, calcFunc-freq, calc-spn, calc-midi and calc-freq.

* doc/misc/calc.tex (Musical Notes): New section.
  (Customizing Calc): Mention calc-note-threshold.
This commit is contained in:
Jay Belanger 2011-03-05 22:28:39 -06:00
parent 479a2c9bfe
commit 05a29101b2
6 changed files with 344 additions and 23 deletions

View file

@ -3,6 +3,9 @@
* calc.texi (Logarithmic Units): Rename calc-logunits-dblevel
and calc-logunits-nplevel to calc-dblevel and calc-nplevel,
respectively.
(Musical Notes): New section.
(Customizing Calc): Mention the customizable variable
calc-note-threshold.
2011-03-03 Glenn Morris <rgm@gnu.org>

View file

@ -27676,6 +27676,7 @@ begin with the @kbd{u} prefix key.
* Predefined Units::
* User-Defined Units::
* Logarithmic Units::
* Musical Notes::
@end menu
@node Basic Operations on Units, The Units Table, Units, Units
@ -28121,7 +28122,7 @@ was already a set of user-defined units in your Calc init file, it
is replaced by the new set. (@xref{General Mode Commands}, for a way to
tell Calc to use a different file for the Calc init file.)
@node Logarithmic Units, , User-Defined Units, Units
@node Logarithmic Units, Musical Notes, User-Defined Units, Units
@section Logarithmic Units
The units @code{dB} (decibels) and @code{Np} (nepers) are logarithmic
@ -28363,6 +28364,76 @@ a logarithmic unit by a number; the @kbd{l /}
logarithmic unit by a number. Note that the reference quantities don't
play a role in this arithmetic.
@node Musical Notes, , Logarithmic Units, Units
@section Musical Notes
Calc can convert between musical notes and their associated
frequencies. Notes can be given using either scientific pitch
notation or midi numbers. Since these note systems are basically
logarithmic scales, Calc uses the @kbd{l} prefix for functions
operating on notes.
Scientific pitch notation refers to a note by giving a letter
A through G, possibly followed by a flat or sharp) with a subscript
indicating an octave number. Each octave starts with C and ends with
B and
@c increasing each note by a semitone will result
@c in the sequence @expr{C}, @expr{C} sharp, @expr{D}, @expr{E} flat, @expr{E},
@c @expr{F}, @expr{F} sharp, @expr{G}, @expr{A} flat, @expr{A}, @expr{B}
@c flat and @expr{B}.
the octave numbered 0 was chosen to correspond to the lowest
audible frequency. Using this system, middle C (about 261.625 Hz)
corresponds to the note @expr{C} in octave 4 and is denoted
@expr{C_4}. Any frequency can be described by giving a note plus an
offset in cents (where a cent is a ratio of frequencies so that a
semitone consists of 100 cents).
The midi note number system assigns numbers to notes so that
@expr{C_(-1)} corresponds to the midi note number 0 and @expr{G_9}
corresponds to the midi note number 127. A midi controller can have
up to 128 keys and each midi note number from 0 to 127 corresponds to
a possible key.
@kindex l s
@pindex calc-spn
@tindex spn
The @kbd{l s} (@code{calc-spn}) [@code{spn}] command converts either
a frequency or a midi number to scientific pitch notation. For
example, @code{500 Hz} gets converted to
@code{B_4 + 21.3094853649 cents} and @code{84} to @code{C_6}.
@kindex l m
@pindex calc-midi
@tindex midi
The @kbd{l m} (@code{calc-midi}) [@code{midi}] command converts either
a frequency or a note given in scientific pitch notation to the
corresponding midi number. For example, @code{C_6} gets converted to 84
and @code{440 Hz} to 69.
@kindex l f
@pindex calc-freq
@tindex freq
The @kbd{l f} (@code{calc-freq}) [@code{freq}] command converts either
either a midi number or a note given in scientific pitch notation to
the corresponding frequency. For example, @code{Asharp_2 + 30 cents}
gets converted to @code{118.578040134 Hz} and @code{55} to
@code{195.99771799 Hz}.
Since the frequencies of notes are not usually given exactly (and are
typically irrational), the customizable variable
@code{calc-note-threshold} determines how close (in cents) a frequency
needs to be to a note to be recognized as that note
(@pxref{Customizing Calc}). This variable has a default value of
@code{1}. For example, middle @var{C} is approximately
@expr{261.625565302 Hz}; this frequency is often shortened to
@expr{261.625 Hz}. Without @code{calc-note-threshold} (or a value of
@expr{0}), Calc would convert @code{261.625 Hz} to scientific pitch
notation @code{B_3 + 99.9962592773 cents}; with the default value of
@code{1}, Calc converts @code{261.625 Hz} to @code{C_4}.
@node Store and Recall, Graphics, Units, Top
@chapter Storing and Recalling
@ -35481,6 +35552,15 @@ and the default value of @code{calc-logunits-field-reference} is
@code{"20 uPa"}.
@end defvar
@defvar calc-note-threshold
See @ref{Musical Notes}.@*
The variable @code{calc-note-threshold} is a number (written as a
string) which determines how close (in cents) a frequency needs to be
to a note to be recognized as that note.
The default value of @code{calc-note-threshold} is 1.
@end defvar
@defvar calc-highlight-selections-with-faces
@defvarx calc-selected-face
@defvarx calc-nonselected-face
@ -36129,26 +36209,29 @@ keystrokes are not listed in this summary.
@r{ v x@: I k T @: @: @:ltpt@:(x,v)}
@c
@r{ a b@: l + @: @: 2 @:lupoweradd@:(a,b)}
@r{ a b@: H l + @: @: 2 @:lufieldadd@:(a,b)}
@r{ a b@: l - @: @: 2 @:lupowersub@:(a,b)}
@r{ a b@: H l - @: @: 2 @:lufieldsub@:(a,b)}
@r{ a b@: l * @: @: 2 @:lupowermul@:(a,b)}
@r{ a b@: H l * @: @: 2 @:lufieldmul@:(a,b)}
@r{ a b@: l / @: @: 2 @:lupowerdiv@:(a,b)}
@r{ a b@: H l / @: @: 2 @:lufielddiv@:(a,b)}
@r{ a@: l d @: @: 1 @:dbpowerlevel@:(a)}
@r{ a b@: O l d @: @: 2 @:dbpowerlevel@:(a,b)}
@r{ a@: H l d @: @: 1 @:dbfieldlevel@:(a)}
@r{ a b@: O H l d @: @: 2 @:dbfieldlevel@:(a,b)}
@r{ a@: l n @: @: 1 @:nppowerlevel@:(a)}
@r{ a b@: O l n @: @: 2 @:nppowerlevel@:(a,b)}
@r{ a@: H l n @: @: 1 @:npfieldlevel@:(a)}
@r{ a b@: O H l n @: @: 2 @:npfieldlevel@:(a,b)}
@r{ a@: l q @: @: 1 @:powerquant@:(a)}
@r{ a b@: O l q @: @: 2 @:powerquant@:(a,b)}
@r{ a@: H l q @: @: 1 @:fieldquant@:(a)}
@r{ a b@: O H l q @: @: 2 @:fieldquant@:(a,b)}
@r{ a b@: l + @: @: @:lupoweradd@:(a,b)}
@r{ a b@: H l + @: @: @:lufieldadd@:(a,b)}
@r{ a b@: l - @: @: @:lupowersub@:(a,b)}
@r{ a b@: H l - @: @: @:lufieldsub@:(a,b)}
@r{ a b@: l * @: @: @:lupowermul@:(a,b)}
@r{ a b@: H l * @: @: @:lufieldmul@:(a,b)}
@r{ a b@: l / @: @: @:lupowerdiv@:(a,b)}
@r{ a b@: H l / @: @: @:lufielddiv@:(a,b)}
@r{ a@: l d @: @: @:dbpowerlevel@:(a)}
@r{ a b@: O l d @: @: @:dbpowerlevel@:(a,b)}
@r{ a@: H l d @: @: @:dbfieldlevel@:(a)}
@r{ a b@: O H l d @: @: @:dbfieldlevel@:(a,b)}
@r{ a@: l n @: @: @:nppowerlevel@:(a)}
@r{ a b@: O l n @: @: @:nppowerlevel@:(a,b)}
@r{ a@: H l n @: @: @:npfieldlevel@:(a)}
@r{ a b@: O H l n @: @: @:npfieldlevel@:(a,b)}
@r{ a@: l q @: @: @:powerquant@:(a)}
@r{ a b@: O l q @: @: @:powerquant@:(a,b)}
@r{ a@: H l q @: @: @:fieldquant@:(a)}
@r{ a b@: O H l q @: @: @:fieldquant@:(a,b)}
@r{ a@: l s @: @: @:spn@:(a)}
@r{ a@: l m @: @: @:midi@:(a)}
@r{ a@: l f @: @: @:freq@:(a)}
@c
@r{ @: m a @: @: 12,13 @:calc-algebraic-mode@:}

View file

@ -3,10 +3,20 @@
* calc/calc-ext.el (calc-init-extensions):
Rename calc-logunits-dblevel and calc-logunits-nplevel to
calc-dblevel and calc-nplevel, respectively.
Add keybindings for calc-spn, calc-midi and calc-freq. Add
autoloads for calcFunc-spn, calcFunc-midi, calcFunc-freq,
calc-spn, calc-midi and calc-freq.
* calc/calc-units.el (calc-dblevel): Rename from
calc-logunits-dblevel.
(calc-nplevel): Rename from calc-logunits-nplevel.
(math-midi-round, math-freqp, math-midip, math-spnp)
(math-spn-to-midi, math-midi-to-spn, math-freq-to-spn)
(math-midi-to-freq, math-spn-to-freq, calcFunc-spn, calcFunc-midi)
(calcFunc-freq, calc-freq, calc-midi, calc-spn): New functions.
(math-notes): New variable.
* calc/calc.el (calc-note-threshold): New variable.
2011-03-06 Chong Yidong <cyd@stupidchicken.com>

View file

@ -429,6 +429,10 @@
(define-key calc-mode-map "l-" 'calc-logunits-sub)
(define-key calc-mode-map "l*" 'calc-logunits-mul)
(define-key calc-mode-map "l/" 'calc-logunits-divide)
(define-key calc-mode-map "ls" 'calc-spn)
(define-key calc-mode-map "lm" 'calc-midi)
(define-key calc-mode-map "lf" 'calc-freq)
(define-key calc-mode-map "l?" 'calc-l-prefix-help)
(define-key calc-mode-map "m" nil)
@ -944,7 +948,7 @@ calcFunc-lupoweradd calcFunc-lufieldsub calcFunc-lupowersub
calcFunc-lufieldmul calcFunc-lupowermul calcFunc-lufielddiv
calcFunc-lupowerdiv calcFunc-fieldquant calcFunc-powerquant
calcFunc-dbfieldlevel calcFunc-dbpowerlevel calcFunc-npfieldlevel
calcFunc-nppowerlevel
calcFunc-nppowerlevel calcFunc-spn calcFunc-midi calcFunc-freq
math-build-units-table math-build-units-table-buffer
math-check-unit-name math-convert-temperature math-convert-units
math-extract-units math-remove-units math-simplify-units
@ -1178,7 +1182,8 @@ calc-get-unit-definition calc-permanent-units calc-quick-units
calc-remove-units calc-simplify-units calc-undefine-unit
calc-view-units-table calc-logunits-quantity calc-dblevel
calc-nplevel calc-logunits-add calc-logunits-sub
calc-logunits-mul calc-logunits-divide)
calc-logunits-mul calc-logunits-divide calc-spn calc-midi
calc-freq)
("calc-vec" calc-arrange-vector calc-build-vector calc-cnorm
calc-conj-transpose calc-cons calc-cross calc-kron calc-diag

View file

@ -1859,6 +1859,221 @@ In symbolic mode, return the list (^ a b)."
(calc-binary-op "lunp" 'calcFunc-nppowerlevel arg)
(calc-unary-op "lunp" 'calcFunc-nppowerlevel arg)))))
;;; Musical notes
(defvar calc-note-threshold)
(defun math-midi-round (num)
"Round NUM to an integer N if NUM is within calc-note-threshold cents of N."
(let* ((n (math-round num))
(diff (math-abs
(math-sub num n))))
(if (< (math-compare diff (math-read-expr calc-note-threshold)) 0)
n
num)))
(defconst math-notes
'(((var C var-C) . 0)
((var Csharp var-Csharp) . 1)
; ((var C♯ var-C♯) . 1)
((var Dflat var-Dflat) . 1)
; ((var D♭ var-D♭) . 1)
((var D var-D) . 2)
((var Dsharp var-Dsharp) . 3)
; ((var D♯ var-D♯) . 3)
((var E var-E) . 4)
((var F var-F) . 5)
((var Fsharp var-Fsharp) . 6)
; ((var F♯ var-F♯) . 6)
((var Gflat var-Gflat) . 6)
; ((var G♭ var-G♭) . 6)
((var G var-G) . 7)
((var Gsharp var-Gsharp) . 8)
; ((var G♯ var-G♯) . 8)
((var A var-A) . 9)
((var Asharp var-Asharp) . 10)
; ((var A♯ var-A♯) . 10)
((var Bflat var-Bflat) . 10)
; ((var B♭ var-B♭) . 10)
((var B var-B) . 11))
"An alist of notes with their number of semitones above C.")
(defun math-freqp (freq)
"Non-nil if FREQ is a positive number times the unit Hz.
If non-nil, return the coefficient of Hz."
(let ((freqcoef (math-simplify-units
(math-div freq '(var Hz var-Hz)))))
(if (Math-posp freqcoef) freqcoef)))
(defun math-midip (num)
"Non-nil if NUM is a possible MIDI note number.
If non-nil, return NUM."
(if (Math-numberp num) num))
(defun math-spnp (spn)
"Non-nil if NUM is a scientific pitch note (note + cents).
If non-nil, return a list consisting of the note and the cents coefficient."
(let (note cents rnote rcents)
(if (eq (car-safe spn) '+)
(setq note (nth 1 spn)
cents (nth 2 spn))
(setq note spn
cents nil))
(cond
((and ;; NOTE is a note, CENTS is nil or cents.
(eq (car-safe note) 'calcFunc-subscr)
(assoc (nth 1 note) math-notes)
(integerp (nth 2 note))
(setq rnote note)
(or
(not cents)
(Math-numberp (setq rcents
(math-simplify
(math-div cents '(var cents var-cents)))))))
(list rnote rcents))
((and ;; CENTS is a note, NOTE is cents.
(eq (car-safe cents) 'calcFunc-subscr)
(assoc (nth 1 cents) math-notes)
(integerp (nth 2 cents))
(setq rnote cents)
(or
(not note)
(Math-numberp (setq rcents
(math-simplify
(math-div note '(var cents var-cents)))))))
(list rnote rcents)))))
(defun math-freq-to-midi (freq)
"Return the midi note number corresponding to FREQ Hz."
(let ((midi (math-add
69
(math-mul
12
(calcFunc-log
(math-div freq 440)
2)))))
(math-midi-round midi)))
(defun math-spn-to-midi (spn)
"Return the MIDI number corresponding to SPN."
(let* ((note (cdr (assoc (nth 1 (car spn)) math-notes)))
(octave (math-add (nth 2 (car spn)) 1))
(cents (nth 1 spn))
(midi (math-add
(math-mul 12 octave)
note)))
(if cents
(math-add midi (math-div cents 100))
midi)))
(defun math-midi-to-spn (midi)
"Return the scientific pitch notation corresponding to midi number MIDI."
(let (midin cents)
(if (math-integerp midi)
(setq midin midi
cents nil)
(setq midin (math-floor midi)
cents (math-mul 100 (math-sub midi midin))))
(let* ((nr ;; This should be (math-idivmod midin 12), but with
;; better behavior for negative midin.
(if (Math-negp midin)
(let ((dm (math-idivmod (math-neg midin) 12)))
(if (= (cdr dm) 0)
(cons (math-neg (car dm)) 0)
(cons
(math-sub (math-neg (car dm)) 1)
(math-sub 12 (cdr dm)))))
(math-idivmod midin 12)))
(n (math-sub (car nr) 1))
(note (car (rassoc (cdr nr) math-notes))))
(if cents
(list '+ (list 'calcFunc-subscr note n)
(list '* cents '(var cents var-cents)))
(list 'calcFunc-subscr note n)))))
(defun math-freq-to-spn (freq)
"Return the scientific pitch notation corresponding to FREQ Hz."
(math-with-extra-prec 3
(math-midi-to-spn (math-freq-to-midi freq))))
(defun math-midi-to-freq (midi)
"Return the frequency of the note with midi number MIDI."
(list '*
(math-mul
440
(math-pow
2
(math-div
(math-sub
midi
69)
12)))
'(var Hz var-Hz)))
(defun math-spn-to-freq (spn)
"Return the frequency of the note with scientific pitch notation SPN."
(math-midi-to-freq (math-spn-to-midi spn)))
(defun calcFunc-spn (expr)
"Return EXPR written as scientific pitch notation + cents."
;; Get the coeffecient of Hz
(let (note)
(cond
((setq note (math-freqp expr))
(math-freq-to-spn note))
((setq note (math-midip expr))
(math-midi-to-spn note))
((math-spnp expr)
expr)
(t
(math-reject-arg expr "*Improper expression")))))
(defun calcFunc-midi (expr)
"Return EXPR written as a MIDI number."
(let (note)
(cond
((setq note (math-freqp expr))
(math-freq-to-midi note))
((setq note (math-spnp expr))
(math-spn-to-midi note))
((math-midip expr)
expr)
(t
(math-reject-arg expr "*Improper expression")))))
(defun calcFunc-freq (expr)
"Return the frequency corresponding to EXPR."
(let (note)
(cond
((setq note (math-midip expr))
(math-midi-to-freq note))
((setq note (math-spnp expr))
(math-spn-to-freq note))
((math-freqp expr)
expr)
(t
(math-reject-arg expr "*Improper expression")))))
(defun calc-freq (arg)
"Return the frequency corresponding to the expression on the stack."
(interactive "P")
(calc-slow-wrapper
(calc-unary-op "freq" 'calcFunc-freq arg)))
(defun calc-midi (arg)
"Return the MIDI number corresponding to the expression on the stack."
(interactive "P")
(calc-slow-wrapper
(calc-unary-op "midi" 'calcFunc-midi arg)))
(defun calc-spn (arg)
"Return the scientific pitch notation corresponding to the expression on the stack."
(interactive "P")
(calc-slow-wrapper
(calc-unary-op "spn" 'calcFunc-spn arg)))
(provide 'calc-units)
;; Local variables:

View file

@ -446,6 +446,11 @@ by displaying the sub-formula in `calc-selected-face'."
:group 'calc
:type '(string))
(defcustom calc-note-threshold "1"
"The number of cents that a frequency should be near a note
to be identified as that note."
:type 'string
:group 'calc)
(defface calc-nonselected-face
'((t :inherit shadow