From: Jay Belanger Date: Sun, 6 Mar 2011 04:28:39 +0000 (-0600) Subject: * calc/calc-units.el (math-midi-round, math-freqp, math-midip) X-Git-Tag: emacs-pretest-24.0.90~104^2~275^2~674^2~2 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=05a29101b26339dd1964938c47a3dc1eb916468c;p=emacs.git * 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. --- diff --git a/doc/misc/ChangeLog b/doc/misc/ChangeLog index bc8fc03d2c8..2c5f998737a 100644 --- a/doc/misc/ChangeLog +++ b/doc/misc/ChangeLog @@ -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 diff --git a/doc/misc/calc.texi b/doc/misc/calc.texi index b5316b8414f..88103fc0034 100644 --- a/doc/misc/calc.texi +++ b/doc/misc/calc.texi @@ -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@:} diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 0beb3db3a44..e34fde79543 100644 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -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 diff --git a/lisp/calc/calc-ext.el b/lisp/calc/calc-ext.el index 30fe3ba6238..11a26d6d125 100644 --- a/lisp/calc/calc-ext.el +++ b/lisp/calc/calc-ext.el @@ -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 diff --git a/lisp/calc/calc-units.el b/lisp/calc/calc-units.el index 4f853546cfd..f022f4f472b 100644 --- a/lisp/calc/calc-units.el +++ b/lisp/calc/calc-units.el @@ -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: diff --git a/lisp/calc/calc.el b/lisp/calc/calc.el index 72ddddeb32d..f4d8983eb88 100644 --- a/lisp/calc/calc.el +++ b/lisp/calc/calc.el @@ -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