synth.el --- music sheet player, synthesizer, music via bash scripts New Features since 0.0 ======================
* beep.el is now (more sanely) known as synth.el, following a name-change suggestion by RMS. * The new letter r also stands for 'rest'. This is an alternative to the z we already had in synth.el. This is for ditty compatibility. * RMS suggested it be made to work with pc speakers as well. I could't find a synth-like program that plays to sound speakers (if you know one, please let me know), so I wrote my own beep_to_speaker.sh (posted separately). Therefore: * There are two new mechanisms to beep via soundcard instead of the pc speaker. These are called "speaker continuous" and "speaker discontinuous". In the synthesizer mode, you can see the currently active speaker mechanism in the modeline. * To choose a speaker mechanism, type M-x synth-options-choose-speaker. As you (will) see, this needs querer.el installed, which I am posting here separately. The alternative is to customise synth-fd->program-function by hand. * When using soundcard, we can now allow for volume control as well. Volume is referred to as magnitude m in synth terminology. (Any volume directives are silently ignored when using the pc speaker) . In your music sheets, use m<num> to increase or decrease the magnitude. (we could not use v for volume, since v is taken for velocity). As a user, you can control overall volume via M-x beep-options-magnitude-<increase/decrease>. * For soundcard, we use the external script beep_to_soundcard.sh instead of synth. That script is posted here separately. To use that, you will need to apt-get install octave2.1 AND apt-get install octave-forge. Drop that script somewhere in your bash path, like ~/bin/ * New demo for loudness variation: M-x synth-demo-loudness ----------------------------------------------------- INTRODUCTION: ============ synth.el is an interface between the music sheet spec and a corresponding synth player. It can help you synthesize music, converting your keystrokes a,bq,c#, etc. to frequency+duration+volume as you type them, and then playing them through your pc-speaker. Or, you can play music from such text-based music files. Or just have fun with the current buffer and see M-x synth-play-buffer what it sounds like. It can generate a bash script for the current music sheet, the script uses synth to play the music, and such scripts can be useful inside more general scripts like crontab reminders. There is currently no interface provided to output the music through regular sound speakers. To listen to some demos, make sure you have a pc-speaker, apt-get install synth, and then type M-x synth-demo-<name>. Next, fiddle with user options like M-x synth-options-octave-increase or M-x synth-options-speed-decrease, M-x synth-options-magnitude-increase, etc. and replay the demo. As you can see, synth.el also generates the equivalent bash scripts for your use for things like crontab reminders. Don't forget to reset options using M-x synth-options-reset (this does not reset the speaker choice). Next, type M-x synthesizer (or M-x synth-mode anywhere else), to also play whatever you type. Then, type something like cv4 (v4 sets the velocity or the speed) followed by a space. Next, type d SPC, e SPC f SPC g SPC a SPC, b SPC and finally co4 SPC (o4 takes us to the 4th octave). Type SPC again at any time to repeat the last sound. At any time, type M-x synth-eval-buffer, or C-c C-c. Magnitude (volume) options take effect only when using sound speakers. M-x synth-options-choose-speaker chooses speaker type, currently, we offer pc-speaker (default) and 2 sound speaker choices. Type M-x synth-commentary for many more comments, syntax, etc. In the next post, also find a synth.sh to call synth.el from bash script, though you should probably hardly ever need to do so, since synth.el generates direct bash script for the song for you. ----------------------------------------------------- The latest version can be had from http://gnufans.net/~deego/emacspub/lisp-mine/synth/ . ;;;---------------- CUT HERE ------------------------------- ;;; synth.el --- music sheet player, synthesizer, music via bash scripts ;; Time-stamp: <2005-07-31 16:30:16 deego> ;; Copyright (C) 2005 D. Goel ;; Emacs Lisp Archive entry ;; Filename: synth.el ;; Package: synth ;; Author: D. Goel <[EMAIL PROTECTED]> ;; Keywords: crontab, bash music, sheet music player, synthesizer ;; Version: 0.1 ;; URL: http://gnufans.net/~deego/emacspub/lisp-mine/synth/ ;; For latest version: (defconst synth-home-page "http://gnufans.net/~deego/emacspub/lisp-mine/synth/") ;; This file is NOT (yet) part of GNU Emacs. ;; This is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation; either version 2, or (at your option) ;; any later version. ;; This is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to the ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330, ;; Boston, MA 02111-1307, USA. ;; ;; See also: ;; Quick start: (defconst synth-quick-start "Type M-x synth-introduction " ) (defun synth-quick-start () "Provides electric help from variable `synth-quick-start'." (interactive) (with-electric-help '(lambda () (insert synth-quick-start) nil) "*doc*")) ;;; Introduction: ;; Stuff that gets posted to gnu.emacs.sources ;; as introduction (defconst synth-introduction "synth.el is an interface between the music sheet spec and a corresponding synth player. It can help you synthesize music, converting your keystrokes a,bq,c#, etc. to frequency+duration+volume as you type them, and then playing them through your pc-speaker. Or, you can play music from such text-based music files. Or just have fun with the current buffer and see M-x synth-play-buffer what it sounds like. It can generate a bash script for the current music sheet, the script uses synth to play the music, and such scripts can be useful inside more general scripts like crontab reminders. There is currently no interface provided to output the music through regular sound speakers. To listen to some demos, make sure you have a pc-speaker, apt-get install synth, and then type M-x synth-demo-<name>. Next, fiddle with user options like M-x synth-options-octave-increase or M-x synth-options-speed-decrease, M-x synth-options-magnitude-increase, etc. and replay the demo. As you can see, synth.el also generates the equivalent bash scripts for your use for things like crontab reminders. Don't forget to reset options using M-x synth-options-reset (this does not reset the speaker choice). Next, type M-x synthesizer (or M-x synth-mode anywhere else), to also play whatever you type. Then, type something like cv4 (v4 sets the velocity or the speed) followed by a space. Next, type d SPC, e SPC f SPC g SPC a SPC, b SPC and finally co4 SPC (o4 takes us to the 4th octave). Type SPC again at any time to repeat the last sound. At any time, type M-x synth-eval-buffer, or C-c C-c. Magnitude (volume) options take effect only when using sound speakers. M-x synth-options-choose-speaker chooses speaker type, currently, we offer pc-speaker (default) and 2 sound speaker choices. Type M-x synth-commentary for many more comments, syntax, etc. In the next post, also find a synth.sh to call synth.el from bash script, though you should probably hardly ever need to do so, since synth.el generates direct bash script for the song for you. " ) ;;;###autoload (defun synth-introduction () "Provides electric help from variable `synth-introduction'." (interactive) (with-electric-help '(lambda () (insert synth-introduction) nil) "*doc*")) ;;; Commentary: (defconst synth-commentary "First, see M-x synth-introduction. * As you have learnt, every /INSTRUCTION/ is a series of characters with no spaces in them, for example, \"ao4\" is an instruction. * A synth file or a SENTENCE is a series of instructions separated by whitespace, for example, the sentence \"v3 ao4 f\" comprises 3 instructions. (The current string or file or buffer being played is called a /SONG/). * The most important (though optional) part of an instruction is the NOTE. Letters c,d,e,f,g,a,b comprise the note (for any octave). The capital versions, C, D, E .. comprise the corresponding sharp versions. * The letter z is treated as a note, except that it means: silence. * The letter r is same as letter z, and means rest. * Next, we discuss modifiers. These modifiers may appear by themselves in an instruction, or they may be combined with other modifiers and possibly a note. * v==> velocity, or speed. Use v to set the speed. Thus, v2 is double the speed of v1, which is double of v0, and so on. In other words, v sets the duration for which each note is played. (we could not use s since s stands for sixteenth, see below) * o==> octave, or tempo. Use o to set the octave. Thus, o3 is one octave to the right of o2 (which means twice the frequency). * m=> magnitude, or volume. m0 is the default. m1 is double of m0, and so on. * Thus, the following sentences are equivalent: \"v3 c v4 a b\" \"v3c av4 b\" == \"cv3 av4 bv4\" == \"cv3 v4a v4 v4 v4 v4b\", you get the idea. For each instruction, the modifiers are processed, and then the note, if present in the instruction, is played. That is all you really need to know to compose your own music in synth.el, type M-x synth-synthesizer and enjoy. The above is the usage we recommend. Next, we discuss various other smaller features and compatibility hacks provided, and other issues: * Any \"junk\" in an instruction is simply ignored. We simply play the part of an instruction that we can parse. But you should never rely on that as a feature. **We will feel free to assign meaning to other letters, etc. in the future.** * Sharps can also be specified by using \"#\" somewhere in the instruction. Thus, a# == A == #a == ##a. We tolerate this form, but prefer using capital versions for sharp. * What about flats? Pitchwise, d flat is nothing but c sharp, so you might as well use C. However, you can also specify d flat as db, in this case, the note must be the very first part of the instruction. Thus, dbo4 == Co4. To repeat, the flat note must be the very first part of the note. Thus, dbo3 is ok, but your b modifier will be ignored if you typed o3db. * All lines beginning with # are ignored. To repeat, only lines BEGINNING with # are ignored. * <num>: A number where it is not otherwise expected is often treated as o<num>. Thus, a4 is same as ao4. This instruction sets the octave to 4 and then plays a in that octave. We currently tolerate this format only when present in original ditty-ike instructions where you don't use our special u, U, p, P commands. Thus, most often, you might want to use things like a4, i.e., a letter followed by a number, though we recommend using ao4 instead. Vn: V followed by a number sets the velocity, or the speed at which we play the current note. The larger the V, the smaller duration the current note will have. Specifically, the duration is directly proportional to (2^(-V)). The number can also be a float and/or negative. Future notes are not affected. v: which we introduced already, is just like V, except that it changes the default duration for future notes (only in the *current song*) as well. Thus, your very first instruction might simply be v3 (to set the speed for the whole melody). Or it might be qe (see later, which is equivalent to tXX). u: Can be used in the middle of a song to reset the current speed to the one at the beginning of the song. U: Sets the speed to the system default speed. You should avoid using U in your songs, so that you allow the user to specify such things as a default speed when playing your song. In fact, we currently bother to not even implement this. M<num>: refers to magnitude, or the volume. 0 is the default. M1 is double of M0, M-1 is half of M0, and so on. m, which we already introduced, is just like M, except the default volume also gets set. O<num>: O followed by a number specifies seta the octave or the tempo (or the frequency multiplier) to use when playing the current note, if any. The typical octave values are 1 to 5. The default octave is 3. The number can also be a float and/or negative. o: , which we already introduced, is just like O, except that it changes the default octave for futures notes in the current song as well. well. This is like ditty's default. p: Can be used somewhere in the middle of a song to reset the current default octave to the one at the beginning of the song. P: Resets the default octave to the system default octave. You should avoid using P in your songs, so that the user can specify such things as default pitch when playing your song. In fact, we currently bother to not even implement this. * Usually, the case convention is chosen so that the smallcase is the one you will use most frequently. These keys are defined for ditty compatibility: w: whole note. Is same as v0. h: half note. Is same as v1. q: quarter note. Same as v2. e: eigth note. Same as v3, whose usage is strongly advised instead. This e has a potential for confusion with the letter e. Thus, your e in an instruction is parsed as an eigth note only if the note had another letter (which may itself be an e) preceding the e. s: sixteenth note. Same as v4. t: thirty-second note. Same as v5. ,: separator, you will probably never use it. This can help you separate two numbers. Thus v3,2 getd parsed as v3o2 instead of v32. * (ditty compliance) multiple t specifications within the same note are additive. Thus, for example, ahq means play the note a for a speed of (half + quarter). That is same as saying av1v2. * Any other \"junk\" in an instruction is silently ignored. * Thus, an example of a valid instruction is e4q, which asks us to play e, make the current and the default octave to be the 4th octave places, and increase the current and set the default speed to 4. (q stands for quarter, which is same as v2). * synth's instruction set is a superset of that of ditty. Sending outputs to pc speakers from a terminal does not work from ditty because of tty issues. synth.el, however, does not suffer from this issue, since, by default, we simply pipe any instructions to a typical system's built-in 'beep -f...' If you do not have synth installed, apt-get install synth. * We don't know yet how to send a freq/duration code to the sound card, but if you know how to send it, simply code it in a synth-fd->program-my, and setq synth-fd->program-function to synth-fd->program-my. * By default, we use the pc's built-in speaker rather than the sound cards, so that synth should be useful for such things as playing appropriate warnings from crontab. * You can also call this file from shell-scripts via a ditty-like interface. See the file synth.sh, whose interface and options are very similar to that of ditty. synth.sh options: :f for filename synth.sh options: :t for speed or duration. Each note is played for that long. * Lisp: M-x synth-play-string is the wrapper which calls the various processors on a string. Processing: Each synth instruction is converted to a synth lisp form, something like '((note \"a\") (octave 2)) from where it is then converted to a series of (frequency,duration) specifiers, from which it gets concveted to shell-commands of the form 'beep -f ...'. All that is finally converted into an executable synth lisp form (shell-command \"beep -f ...\"). * You can also use synth to see how to convert strings to their bash synth command equivalents. These equivalents can then be used in, or made into bash scripts. The *synth-log* stores these every time you play some string via synth. * For some sick fun, take a random buffer and play it to see how it sounds. Then, take its bash output and synth it again, and so on. * It is most instructive to see how the source code of M-x synth-demo-updown works. The source code is explained there. See also synth-demo-updown2. NOTHING IN THIS PROGRAM IS RESTRICTED TO INTEGERS or to positive numbers. FEEL FREE TO USE FLOATS FOR *ANY* ARGUEMNTS FOR FINER EFFECTS, including octave, velocity, magnitude etc. To customize, customize all variables defined with a defcustom, but leave the defvars alone. " ) (defun synth-commentary () "Provides electric help from variable `synth-commentary'." (interactive) (with-electric-help '(lambda () (insert synth-commentary) nil) "*doc*")) ;;; History: ;;; Bugs: ;;; New features: (defconst synth-new-features "New Features since 0.0 ====================== * beep.el is now (more sanely) known as synth.el, following a name-change suggestion by RMS. * The new letter r also stands for 'rest'. This is an alternative to the z we already had in synth.el. This is for ditty compatibility. * RMS suggested it be made to work with pc speakers as well. I could't find a synth-like program that plays to sound speakers (if you know one, please let me know), so I wrote my own beep_to_speaker.sh (posted separately). Therefore: * There are two new mechanisms to beep via soundcard instead of the pc speaker. These are called \"speaker continuous\" and \"speaker discontinuous\". In the synthesizer mode, you can see the currently active speaker mechanism in the modeline. * To choose a speaker mechanism, type M-x synth-options-choose-speaker. As you (will) see, this needs querer.el installed, which I am posting here separately. The alternative is to customise synth-fd->program-function by hand. * When using soundcard, we can now allow for volume control as well. Volume is referred to as magnitude m in synth terminology. (Any volume directives are silently ignored when using the pc speaker) . In your music sheets, use m<num> to increase or decrease the magnitude. (we could not use v for volume, since v is taken for velocity). As a user, you can control overall volume via M-x beep-options-magnitude-<increase/decrease>. * For soundcard, we use the external script beep_to_soundcard.sh instead of synth. That script is posted here separately. To use that, you will need to apt-get install octave2.1 AND apt-get install octave-forge. Drop that script somewhere in your bash path, like ~/bin/ * New demo for loudness variation: M-x synth-demo-loudness " "" ) (defun synth-new-features () "Provides electric help from variable `synth-new-features'." (interactive) (with-electric-help '(lambda () (insert synth-new-features) nil) "*doc*")) ;;; TO DO: (defconst synth-todo "Help..." ) (defun synth-todo () "Provides electric help from variable `synth-todo'." (interactive) (with-electric-help '(lambda () (insert synth-todo) nil) "*doc*")) (defconst synth-version "0.1") (defun synth-version (&optional arg) "Display synth's version string. With prefix ARG, insert version string into current buffer at point." (interactive "P") (if arg (insert (message "synth version %s" synth-version)) (message "synth version %s" synth-version))) ;;========================================== ;;; Requires: (eval-when-compile (require 'cl)) ;;; Code: (defgroup synth nil "The group synth." :group 'applications) (defcustom synth-before-load-hook nil "Hook to run before loading synth." :group 'synth) (defcustom synth-after-load-hook nil "Hook to run after loading synth." :group 'synth) (run-hooks 'synth-before-load-hook) (defcustom synth-verbosity 100 "How verbose to be. Once you are experienced with this lib, 0 is the recommended value. Values between -90 to +90 are recommended for general use and the rest for debugging." :type 'integer :group 'synth) (defcustom synth-interactivity 0 "How interactive to be. Once you are experienced with this lib, 0 is the recommended value. Values between -90 and +90 are recommended for general use and the rest for debugging." :type 'integer :group 'synth) (defcustom synth-y-or-n-p-function 'synth-y-or-n-p "Function to use for interactivity-dependent `y-or-n-p'. Format same as that of `synth-y-or-n-p'." :type 'function :group 'synth) (defcustom synth-n-or-y-p-function 'synth-n-or-y-p "Function to use for interactivity-dependent `n-or-y-p'. Format same as that of `synth-n-or-y-p'." :type 'function :group 'synth) (defun synth-message (points &rest args) "Signal message, depending on POINTS and `synth-verbosity'. ARGS are passed to `message'." (unless (minusp (+ points synth-verbosity)) (apply #'message args))) (defun synth-y-or-n-p (add prompt) "Query or assume t, based on `synth-interactivity'. ADD is added to `synth-interactivity' to decide whether to query using PROMPT, or just return t." (if (minusp (+ add synth-interactivity)) t (funcall 'y-or-n-p prompt))) (defun synth-n-or-y-p (add prompt) "Query or assume t, based on `synth-interactivity'. ADD is added to `synth-interactivity' to decide whether to query using PROMPT, or just return t." (if (minusp (+ add synth-interactivity)) nil (funcall 'y-or-n-p prompt))) ;;; Real Code: (defun synth-remove-comments (str) "Remove comments from a string." (let* ((strlines (split-string str "\n")) (strlines2 (remove-if #'(lambda (s) (string-match "^#" s)) strlines))) (mapconcat 'identity strlines2 "\n"))) (defvar synth-log-current-string nil) (defvar synth-log-current-stringlist nil) (defun synth-check-maybe () (when (> synth-verbosity 30) (let ((synthp (> (length (shell-command-to-string "which beep")) 3))) (unless synthp (ding t) (message "You do not seem to have beep installed!") (sit-for 1) (ding t) (message "Please apt-get install beep first. ") (sit-for 1))))) (defcustom synth-string->program-function 'synth-string->program-function-default "" :group 'synth) (defun synth-play-string (str) (interactive "s") ;;(synth-check-maybe) (synth-log-clear) (let* ( (synth-log-current-string str) (program (funcall synth-string->program-function str))) (when (> synth-verbosity 60) (set-buffer "*synth-log*") (goto-char (point-min)) (display-buffer "*synth-log*") (sit-for 0.01) ) (eval program))) (defcustom synth-fd->program-function-choices '(synth-fd->program-synth-pcspeaker synth-fd->program-synth-soundcard-continuous synth-fd->program-synth-soundcard-discontinuous ) "" :group 'synth) (defcustom synth-fd->program-function 'synth-fd->program-synth-pcspeaker "Frob this variable for your custom synth solutions. You might want to use M-x `synth-choose-speaker' to set this variable for the current session. See the code of `synth-fd->program-default' for an example. That code is just a wrapper for the 4 processing stages. In your custom solution, you can replace any of those stages. If you come up with a nice alternative to anything, do contribute it to synth.el " :group 'synth ;; why doesn't this work as I would expect? ;;:options synth-fd->program-function-choices ;;:type '(choice (const abc bcd)) ) (defun synth-string->program-function-default (str) (funcall synth-fd->program-function (synth-lisp->freqduration (synth-notes->lisp str)))) (defun synth-fd->program-synth (fd) (synth-shell-commands->program (synth-freqduration->shell-commands fd))) (defalias 'synth-fd->program-synth-pcspeaker 'synth-fd->program-synth) (defun synth-fd->program-synth-soundcard-discontinuous (fd) (let ((synth-command-format synth-command-format-synth-soundcard)) (synth-fd->program-synth fd))) (defun synth-fd->program-synth-soundcard-continuous (fd) (synth-shell-commands->program (synth-freqduration->shell-commands-synth-soundcard-continuous fd))) (defun synth-choose-speaker () (interactive) (let ( ;;(choices synth-fd->program-function-choices) (qp (ignore-errors (require 'querer)))) (unless qp (error "This function needs querer.el installed. Alternatively, you can use defcustom on `synth-sring-to-program-function'. The final alternative is to manually setq it in .emacs")) (let* ( (querer-bindings-extra nil) (qresult (querer-auto-eval (mapcar (lambda (a) `(quote ,a)) synth-fd->program-function-choices) 'auto "Type C-g to abort" (mapcar 'synth-get-nick-speaker-type synth-fd->program-function-choices)))) (if (functionp qresult) (setq synth-fd->program-function qresult) (error "You did not select a speaker type. I will leave italone.")) (message "Synth main program set to %s" synth-fd->program-function)))) (defalias 'synth-fd->program-default 'synth-fd->program-synth) (defcustom synth-command-format "beep -f %s -l %s" "Leave this variable alone unless you know what you are doing. " :group 'synth ) (defcustom synth-command-format-synth-soundcard "beep_to_speaker.sh %s %s %s" "Leave this variable alone unless you know what you are doing. " :group 'synth ) (defun synth-freqduration->shell-commands (fdm) "Negative freq, means no-op." (let ((shells (mapcar (lambda (arg) (let ((f (car arg)) (d (second arg)) (m (third arg)) ) (if (or (< f 0) (= d 0)) ;; either use this or use format %s.. (copy-sequence "## true;") (when (< f 0.01) (setq f 0.01)) (when (> f 19999) (setq f 19999)) (format synth-command-format f d m)))) fdm))) (synth-log-as-bash-script ;;(mapconcat 'identity shells "\n") (mapconcat 'identity (mapcar* (lambda (a b) (concat a " # " b)) shells synth-log-current-stringlist) "\n")) shells)) ;;;###autoload (defun synth-flatten-tree (tree) "Copied by D. Goel from erbutils.el, by the same author" (cond ((null tree) nil) ((listp tree) (apply 'append (mapcar 'synth-flatten-tree tree))) (t (list tree)))) (defun synth-freqduration->shell-commands-synth-soundcard-continuous (fdms) "Negative freq, means no-op." (let* ((fdms2 (copy-tree fdms)) fdmthis (fdmsflat nil) shellcmd ) (while fdms2 (setq fdmthis (pop fdms2)) (while (not (= 3 (length fdmthis))) (setq fdmthis (append fdmthis (list -1)))) (setq fdmsflat (append fdmsflat fdmthis))) (setq shellcmd (concat "beep_to_speaker.sh " (mapconcat #'(lambda (n) (format "%s" n)) fdmsflat " "))) (synth-log-as-bash-script ;;(mapconcat 'identity shells "\n") (concat shellcmd "\n")) (list shellcmd))) (defun synth-log-as-bash-script (bashcommands) (synth-log (concat "#!/bin/bash" "\n" (if synth-bash-name (format "## %s\n" synth-bash-name) "") "## This bash script was generated by synth.el on " (format-time-string "%Y-%m-%d T%T%z\n") (if (stringp synth-log-current-string) (concat "## from the following notes: \n" (replace-regexp-in-string "^" "## " (synth-utils-singlify-newlines synth-log-current-string))) "") "\n\n" bashcommands "\n\n\n") t)) (defun synth-utils-singlify-newlines (str) (with-temp-buffer (insert str) (while (progn (goto-char (point-min)) (search-forward "\n\n" nil t)) (replace-match "\n")) (buffer-substring-no-properties (point-min) (point-max)))) (defun synth-log (arg &optional suppressp) (save-excursion (set-buffer (get-buffer-create "*synth-log*")) (ignore-errors (shell-script-mode)) (goto-char (point-max)) (unless suppressp (insert "\n\n__________________________________________________________") (insert (format-time-string "\n%Y-%m-%d T%T%z")) (insert "\n")) (if (stringp arg) (insert arg) (insert (format "%S" arg))))) (defun synth-log-clear () (save-excursion (set-buffer (get-buffer-create "*synth-log*")) (delete-region (point-min) (point-max)))) (defcustom synth-shell-buffer "*synth-shell-commands*" "" :group 'synth ) (defun synth-shell-commands->program (cmds) (cons 'progn (mapcar (lambda (arg) `(shell-command ,arg ,synth-shell-buffer)) cmds))) (defun synth-debug-string->freqduration (str) (synth-lisp->freqduration (synth-notes->lisp str))) (defun synth-notes->lisp (str) (let* ( ;;(strlines ;;(split-string str "\n")) ;;(strlines2 ;;(synth-remove-comments str)) ;;(remove-if ;;(lambda (str) (string-match "^#" str)) ;;strlines)) (str2 (synth-remove-comments str)) (strs (remove "" (split-string str2 "[ \t\n\r\v]+")))) (synth-notes->lisp-from-stringlist strs))) (defun synth-play-file (file) (interactive "f") (with-temp-buffer (insert-file-contents file) (synth-play-buffer))) (defun synth-play-buffer () (interactive) (synth-play-string (buffer-substring-no-properties (point-min) (point-max)))) (defun synth-play-region (a b) (interactive "r") (synth-play-string (buffer-substring-no-properties a b))) (defcustom synth-bash-name nil "When set to a string, adds that comment to the bash script." :group 'synth ) (defun synth-notes->lisp-from-stringlist (commands) (setq synth-log-current-stringlist commands) (mapcar 'synth-note-to-lisp-ignore-errors commands)) (defconst synth-duration 2000.00 "change speed instead, no need to change this var. ") (defconst synth-volume 400.00 "change magnitude instead, no need to change this var. ") ;;;==================================================== (defcustom synth-param-magnitude-default 0 "" :group 'synth ) (defcustom synth-param-magnitude-user 0 "" :group 'synth) (defcustom synth-param-magnitude synth-param-magnitude-default "Current param" :group 'synth ) (defcustom synth-param-magnitude-song synth-param-magnitude-default "starting param at the beginning of a song." :group 'synth ) ;;==================================================== (defcustom synth-param-speed-default 0 "" :group 'synth ) (defcustom synth-param-speed-user 0 "" :group 'synth) (defcustom synth-param-speed synth-param-speed-default "Current param" :group 'synth ) (defcustom synth-param-speed-song synth-param-speed-default "starting param at the beginning of a song." :group 'synth ) ;;;==================================================== (defcustom synth-param-octave-default 3 "" :group 'synth) (defcustom synth-param-octave-user 0 "How much the user wants the frequency displaced to the right." :group 'synth ) (defcustom synth-param-octave synth-param-octave-default "Current param." :group 'synth ) (defcustom synth-param-octave-song synth-param-octave-default "Starting param at the beginning of a song." :group 'synth ) ;;;==================================================== (defcustom synth-param-magnitude-default 3 "" :group 'synth) (defcustom synth-param-magnitude-user 0 "How much the user wants the frequency displaced to the right." :group 'synth ) (defcustom synth-param-magnitude synth-param-magnitude-default "Current param." :group 'synth ) (defcustom synth-param-magnitude-song synth-param-magnitude-default "Starting param at the beginning of a song." :group 'synth ) ;;;==================================================== (defun synth-options-magnitude-increase (&optional num) "Increase volume" (interactive "p") (setq synth-param-magnitude-user (+ num synth-param-magnitude-user)) (message "User Magnitude set to %s" synth-param-magnitude-song)) (defun synth-options-magnitude-decrease (&optional num) (interactive "p") (synth-options-magnitude-increase (- num))) ;;;==================================================== (defun synth-options-speed-increase (&optional num) (interactive "p") (setq synth-param-speed-user (+ num synth-param-speed-user)) (message "User Speed set to %s" synth-param-speed-song)) (defun synth-options-speed-decrease (&optional num) (interactive "p") (synth-options-speed-increase (- num))) ;;;==================================================== (defun synth-options-octave-increase (num) (interactive "p") (setq synth-param-octave-user (+ num synth-param-octave-user))) (defalias 'synth-options-tempo-increase 'synth-options-octave-increase) (defun synth-options-octave-decrease (num) (interactive "p") (synth-options-octave-increase (- num))) (defalias 'synth-options-tempo-decrease 'synth-options-octave-decrease) (defun synth-options-reset () "This does not reset the speaker choice." (interactive) (setq synth-param-magnitude-user 0) (setq synth-param-octave-user 0) (setq synth-param-speed-user 0)) ;;;==================================================== (defun synth-reset () "No need to ever use this." (synth-reset-octave) (synth-reset-speed)) (defun synth-reset-octave () "No need to ever use this." (interactive) (synth-reset-octave-song) (setq synth-param-octave synth-param-octave-default) ) (defun synth-reset-octave-song () (setq synth-param-octave synth-param-octave-song)) (defun synth-reset-speed () "No need to ever use this." (interactive) (synth-reset-speed-song) (setq synth-param-speed synth-param-speed-default) ) (defun synth-reset-speed-song () (setq synth-param-speed synth-param-speed-song)) (defun synth-note-to-lisp-ignore-errors (&rest args) (ignore-errors (apply 'synth-note-to-lisp args))) (defun synth-note-to-lisp (note) (assert (stringp note)) ;; convert any but the first e's into v3's. (while (string-match "^.*[a-gA-GrzRZ].*\\(e\\)" note) (setq note (replace-match "v3" nil nil note 1))) ;; convert all flats to regulars or sharps: (cond ((string-match "^cb" note) (setq note (replace-match "c" nil nil note))) ((string-match "^db" note) (setq note (replace-match "C" nil nil note))) ((string-match "^eb" note) (setq note (replace-match "D" nil nil note))) ((string-match "^fb" note) (setq note (replace-match "f" nil nil note))) ((string-match "^gb" note) (setq note (replace-match "F" nil nil note))) ((string-match "^ab" note) (setq note (replace-match "G" nil nil note))) ((string-match "^bb" note) (setq note (replace-match "A" nil nil note)))) (let* ( (sharpp (string-match "#" note)) (subnotes (synth-note-to-lisp-break-note note)) (subnoteslisp1 (apply 'append (mapcar (if sharpp 'synth-subnote-to-lisp-sharped 'synth-subnote-to-lisp ) subnotes)))) (synth-lisp-note-cleanup subnoteslisp1))) (defun synth-lisp-note-cleanup (note) "Attempt to clean up a lispy note... Removing, for example, multiple letter specs. If there are more than 1, remove all but the first." (let ((aa (member* 'letter note :key 'car))) (if aa (cons aa (remove* aa note :test #'equal))) note)) (defun synth-note-to-lisp-break-note (note) "Break note into subnotes..." (let* ((notenewline (replace-regexp-in-string "[a-zA-Z]" "\n\\&" note))) (remove "" (split-string notenewline "\n")))) (defvar synth-subnote-to-lisp-sharpp nil) (defun synth-subnote-to-lisp-sharped (subnote) (let ((synth-subnote-to-lisp-sharpp t)) (synth-subnote-to-lisp subnote))) (defun synth-subnote-to-lisp (subnote) "Returns a list of lisp expression(s) resulting from a subnote. The list should typically be of length 1, but sometimes it is nil, and sometimes its length is 2" (let (bc1 bcreststr bcrest (restnum 0) (len (length subnote)) (bcs (string-to-list subnote)) (num (string-to-number subnote))) (if (= len 0) nil (progn (setq bc1 (first bcs) bcrest (rest bcs)) (setq bcreststr (if bcrest (apply 'string bcrest) "")) (if bcreststr (setq restnum (string-to-number bcreststr))) (cond ((member bc1 '(?a ?b ?c ?d ?e ?f ?g ?A ?B ?C ?D ?E ?F ?G ?r ?R ?z ?Z)) (append (list (list 'synth-command-letter (intern (string (if synth-subnote-to-lisp-sharpp (upcase bc1) bc1))))) ;; The rest of the string may be a number.. in which ;; case it should be parsed. (synth-subnote-to-lisp bcreststr))) ((member bc1 '(?m)) (list (list 'synth-command-m restnum))) ((member bc1 '(?M)) (list (list 'synth-command-M restnum))) ((member bc1 '(?o)) (list (list 'synth-command-o restnum))) ((member bc1 '(?O)) (list (list 'synth-command-O restnum))) ((member bc1 '(?v)) (list (list 'synth-command-v restnum))) ((member bc1 '(?V)) (list (list 'synth-command-V restnum))) ;; add sharps to the command.. even though sharpening should have already ;; been taken care of.. ((member bc1 '(?#)) (append (list (list 'synth-command-sharp)) (synth-subnote-to-lisp bcreststr))) ((member bc1 '(?w)) (list (list 'synth-command-v 0))) ((member bc1 '(?h)) (list (list 'synth-command-v 1))) ((member bc1 '(?q)) (list (list 'synth-command-v 2))) ;; ?e, eigths should already have been processed. ((member bc1 '(?s)) (list (list 'synth-command-v 4))) ((member bc1 '(?t)) (list (list 'synth-command-v 5))) ((member bc1 '(?u)) (list (list 'synth-command-u))) ((member bc1 '(?U)) (append (list (list 'synth-command-U)))) ((member bc1 '(?p)) (append (list (list 'synth-command-p)))) ((member bc1 '(?P)) (append (list (list 'synth-command-P)))) ((and (member bc1 '(?1 ?2 ?3 ?4 ?5)) (member num '(1 2 3 4 5))) (list (list 'synth-command-o num))) (t nil)) )))) (defun synth-song-defaults () (setq synth-param-magnitude synth-param-magnitude-song) (setq synth-param-speed synth-param-speed-song) (setq synth-param-octave synth-param-octave-default)) (defun synth-lisp->freqduration (notes) "The input notes are lispy notes." ;; first set current values to defaults, if not already the case. (synth-song-defaults) ;; Next, for each lisp command, find the appropriate frequency and duration. (mapcar 'synth-lisp->freqduration-once notes)) (defvar synth-working-freq nil) (defvar synth-working-duration nil) (defvar synth-working-speed nil) (defvar synth-working-speed-save-p nil) (defvar synth-working-speed-specified-p nil) (defvar synth-working-speed-working nil) (defvar synth-working-octave nil) (defvar synth-working-magnitude nil) (defvar synth-working-found-letter-p nil) (defun synth-lisp->freqduration-once (note) (if note (let ((synth-working-freq 1) (synth-working-octave synth-param-octave) (synth-working-magnitude synth-param-magnitude) (synth-working-speed synth-param-speed) (synth-working-found-letter-p nil) (synth-working-duration synth-duration) (synth-working-speed-save-p nil) (synth-working-speed-specified-p nil) (synth-working-speed-working nil) ) (synth-lisp->freqduration-once-process note) (when synth-working-speed-specified-p (setq synth-working-speed (synth-working-speed-from-speed-list synth-working-speed-working)) (when synth-working-speed-save-p (setq synth-param-speed synth-working-speed))) (if synth-working-found-letter-p (list ;; the current freq already corresponds to an octave of 3 by ;; default, so remove 3 before changing it any more,.. (* synth-working-freq (expt 2.0 (+ synth-working-octave synth-param-octave-user -3))) (/ (float synth-working-duration) (expt 2.0 (+ synth-working-speed synth-param-speed-user))) (* (float synth-volume) (expt 2.0 (+ synth-working-magnitude synth-param-magnitude-user))) ) (list -1 0 0))) (list -1 0 0))) (defun synth-log2 (arg) (/ (log arg) (log 2)) ) (defun synth-working-speed-from-speed-list (tlist) "Want to make speeds additive. So that if a user specifies a speed of hq (1,2) which means half+quarter, the result comes out to what we expect. " (synth-log2 (/ 1.0 (apply '+ (mapcar (lambda (arg) (expt 2.0 (- arg))) tlist))))) (defun synth-lisp->freqduration-once-process (note) (mapc 'synth-lisp->freqduration-once-process-subnote note)) (defun synth-lisp->freqduration-once-process-subnote (subnotes) ;; we should never have to see things like dflat, etc, here, since ;; we should have already converted those. But, if the user ;; directly likes to specify lisp, we might as well process things ;; like 'dflat again.. (let ((sym (car subnotes )) (arg (cadr subnotes))) (case sym (synth-command-letter (setq synth-working-found-letter-p t) (setq synth-working-freq (case arg ;;(?a 220.0) ;;(?A 233.1) ;; See ;; http://www.physics.mcgill.ca/~guymoore/ph224/different_scales.html ;; We use the tempered scale here. ;; NO CFLAT so make it same as c (cflat 261.55) ;; 1.05946 (c 261.55) ;; the base frequency 1 ;; sharp 2187/2048 ;;(C 277.2) ;; (C 277.101763000000) ;; 1.05946 (dflat 277.101763000000) ;; 1.05946 ;;; (dflat 277.2) ;; d flat == c sharp. ;;(d 293.7) (d 293.579413000000) ;; 1.12246 ;; (D 311.1) (D 311.035260000000) ;; 1.18920 (eflat 311.035260000000) ;; same as D# ;;(e 329.6) (e 329.532076000000) ;; 1.25992 ;;there is NO E SHARP, so make it same ;; as e (E 329.532076000000) ;; NO F FLAT so make it same as f (fflat 349.124786500000) ;; 1.33483 (f 349.124786500000) ;; 1.33483 (F 369.886625500000) ;; 1.41421 (gflat 369.886625500000) ;; gflat==gsharp (g 391.880365000000) ;1.49830 ;;(G 415.3) (G 415.184470000000) ;; 1.58740 (aflat 415.184470000000) ;; same as gsharp (a 439.872174500000) ;; 1.68179 (A 466.027174500000) ;; 1.78179 (bflat 466.027174500000) ;; ==asharp (b 493.738397000000) ;; 1.88774 (B 493.738397000000) ;; NO BSHARP!! (r 0.01); pause (R 0.01); pause (z 0.01); pause (Z 0.01); pause ;;C 523.2 (otherwise 1)))) (synth-command-M (setq synth-working-magnitude arg)) (synth-command-m (setq synth-working-magnitude arg) (setq synth-param-magnitude arg)) (synth-command-O (setq synth-working-octave arg)) (synth-command-o (setq synth-working-octave arg) (setq synth-param-octave arg)) (synth-command-V (setq synth-working-speed-specified-p t) (add-to-list 'synth-working-speed-working arg)) (synth-command-v (setq synth-working-speed-specified-p t) (add-to-list 'synth-working-speed-working arg) (setq synth-working-speed-save-p t)) ;;(setq synth-param-speed arg)) (synth-command-u (setq synth-working-speed synth-param-speed-song) (setq synth-param-speed synth-param-speed-song)) (synth-command-p (setq synth-working-octave synth-param-octave-song) (setq synth-param-octave synth-param-octave-song)) (otherwise nil)))) (defun synth-work-frequency (freq) (synth-debug freq) (shell-command (format "synth -f %d -l %d" freq synth-duration))) (defvar synth-debug-p nil) (defun synth-debug (&rest args) (message "DEBUG: %S" args)) ;;;==================================================== (defun synth-reverse-string (str) "Reverse the order of the notes in a string. " (let* ((str2 (synth-remove-comments str)) (strs (split-string str2)) (strsr (reverse strs))) (mapconcat 'identity strsr " "))) ;;;###autoload (defun synth-demo () "The files for furelise , notes.dit and o_susanna.dit are copied from ditty's examples/ directory. The author has released all rights to these files. Accompanying files named *.dit are files you may input into ditty. These files are, as an exception, exempted from the LICENSE - I release all rights to these, and they may be distributed as being in the public domain. Distributions of this software need not include these files - they are meant only for example. " (interactive) (message "Please type M-x synth-demo-<name> instead.")) (defun synth-demo-furelise () (interactive) (let ((synth-verbosity 100) (synth-bash-name "Fur Elise")) (synth-play-string " e4e d# e d# e b3 d4 c a3qe ce e a bqe ee g# b c4qe e3e e4e d# e d# e b3 d4 c a3qe ce e a bqe ee c4 b3 aqe be c4 d eqe g3e f4e e dqe f3e e4 d cqe e3e d4 c b3h e4e d# e d# e b3 d4 c a3qe ce e a bqe ee g# b c4qe e3e e4e d# e d# e b3 d4 c a3qe ce e a bqe ee c4 b3 aqe"))) (defun synth-demo-notes () (interactive) (let ((synth-verbosity 100) (synth-bash-name "NOTES")) (synth-play-string "c2q ebes ce cs fe c bb1 c2q ges ce cs abe g eb c g c3 c2s bb1e bb1s ge d2 cq"))) (defun synth-demo-updown () (interactive) (let ((synth-verbosity 100) (synth-bash-name "Up Down")) (synth-play-string "co3v3 d e f g a b co4 z co4 bo3 a g f e d c ## Note that co3e==co3v2==c3e. (e for eigth) ## co3v3 just sets the current octave: 3, and the current duration: e ## for eigth, which is same as v3. ## The 3 in c3e was optional, since the default octave was already 3. ## Further notes are played with the same octave and duration as the ## current one. That is, until we reach the next end of the ## spectrum. There we increase the octave by 1, to make it 4. Next, ## we pause for a moment via z. Next, we have to come back. So, we ##stick with c, but next, we have to tell it to lower the octave. So, we ##stick a 3 with b, to make it b3 or bo3. We could have also done o3 b "))) (defun synth-demo-updown2 () (interactive) (let ((synth-verbosity 100) (synth-bash-name "Up Down 2")) (synth-play-string "v3 c d e zv5 v3 d e f zv5 v3 v3 e f g zv5 v3 v3 f g a zv5 v3 v3 g a b zv5 v3 a b c4 zv3 v3 c4 b3 a zv5 v3 b a g zv5 v3 a g f zv5 v3 g f e zv5 v3 f e d zv5 v3 e d c zv5 ## Newlines are used just for coding clarity. ## Before At each triplet, we set the default speed to 3. ## Except at the end, where we pause for a smaller moment via v5. ## If we had used V5, we could have avoided most of the remaining v3s ## because V would have left the default alone."))) (defun synth-demo-loudness () (interactive) (let ((synth-verbosity 100) (synth-bash-name "Up Down") ;; this is normaally done by M-x synth-choose-speaker (synth-fd->program-function 'synth-fd->program-synth-soundcard-continuous )) (synth-play-string "v3 o3 am-4 am-3 am-2 am-2.5 am-1 am-1.5 am-.5 am0 am.5 am1 am1.5 am2 am2.5 am3 am4 am5 am5 am4 am3 am2.5 am2 am1.5 am1 am.5 am0 am-.5 am-1 am-1.5 am-2 am-2.5 am-3 am-4 ## play the same note but with different volumes "))) (defun synth-demo-o_susanna () (interactive) (let ((synth-verbosity 100)) (synth-play-string ;; in this string, we start by increasing the at the beginning ;; (from its default of 3) ;; Of course, note that that happens only for the duration of the ;; song. "o4 cqe de eq g gqe ae gq e cqe de eq e d c dh cqe de eq g gqe ae gq e cqe de eq e d d cw fh f aq ah aq g g e c dh cqe de eq g gqe ae gq e cqe de eq e d d c"))) ;;==================================================== (defcustom synth-synth-buffer "*synth-synthesizer*" "" :group 'synth) (defvar synth-mode-map-default '(keymap)) (define-key synth-mode-map-default (kbd "SPC") 'synth-self-insert-space) (define-key synth-mode-map-default (kbd "C-c C-c") 'synth-play-buffer) (define-key synth-mode-map-default (kbd "C-c C-x C-e") 'synth-play-region) (defcustom synth-mode-map synth-mode-map-default "Change this to what yoyu like inn your .emacs." :group 'synth) (defun synth-self-insert-space () (interactive) (insert " ") (synth-synth-play-note-at-point)) (defun synth-synth-play-note-at-point () (let* ((str (buffer-substring-no-properties (point-min) (point))) (fd (synth-lisp->freqduration (synth-notes->lisp str)))) (eval (funcall synth-fd->program-function (last fd))))) (defun synth-get-nick-speaker-type (fn) (let ((fstr (format "%s" fn))) (if (string-match "^synth-fd->program-\\(.*\\)$" fstr) (replace-regexp-in-string "-" " " (match-string 1 fstr)) fstr))) (defcustom synth-mode-string "SYNTH" "" :group 'synth) (defcustom synth-mode-string-format " %s (%s)" "" :group 'synth) ;;;###autoload (easy-mmode-define-minor-mode synth-mode "The mode to inherit minibuffer keybindings" nil (:eval (format synth-mode-string-format synth-mode-string (synth-get-nick-speaker-type synth-fd->program-function))) ;; 3 means C-c ;; 16 means C-p 'synth-mode-map) (defcustom synth-synth-init-string "\nv3 o3 m0 " "" :group 'synth) ;;;###autoload (defun synth-synthesizer () (interactive) (switch-to-buffer (get-buffer-create synth-synth-buffer)) (text-mode) (goto-char (point-max)) (insert synth-synth-init-string) (synth-mode 1)) (defalias 'synth-player 'synth-synthesizer) ;;;###autoload (defalias 'synthesizer 'synth-synthesizer) ;; ==================================================== (defun synth-sh-help () (require 'shs) (shsm "Syntax: synth.sh [:o <num>] [:v <num>] (:f file) || (:p <string>)> Use :o to specify octave number, v for velocity (speed). :f to play a file, or :p to specify a string to play. Do quote the string. Use :h for this help. For some example usage from bash commandline, see the source code of the file synth.sh ")) (defun* synth-sh (&key (o 0) (v 0) help h f p) " Note that you don't need this. synth.el can generate bash equivalents for you which you can directly use from bash in any case. But, if you still want to be able to call synth.el from bash, you can use this function and the attached file synth.sh. Useful for running synth.el from commandline or scripts. Install shs.el, shs-utils.el, synth.sh, and type something like: synth.sh :p \"v3 o3 c d e f g a b c4\" The keyword o INCREASES the overall octave by its argument. The keyword v INCREASES the overall speed by its argument. In other words, they both modify synth-.*-user variables. " ;; synth.sh :p \"v3 o3 c d e f g a b c4\" (require 'shs-utils) (let ((synth-tmp 0)) (if (or h help (or (and (null f) (null p))) (string-match "help" (format "%s%s%s%s" o v h help))) (synth-sh-help) ;; else (setq synth-param-speed-user (shsu-arg-numerify v)) (setq synth-param-octave-user (shsu-arg-numerify o)) (cond (f (synth-play-file f)) (t (synth-play-string (format "%s" p))))))) (defalias 'synth-options-choose-speaker 'synth-choose-speaker) (provide 'synth) (run-hooks 'synth-after-load-hook) ;;; synth.el ends here _______________________________________________ Gnu-emacs-sources mailing list Gnu-emacs-sources@gnu.org http://lists.gnu.org/mailman/listinfo/gnu-emacs-sources