ff-ad9833/notes.fs

213 lines
6.7 KiB
Forth

\ **********************************************************************
\
\ notes.fs is part of the ff-ad9833 project
\ ff-ad9833 is an audio project for FlashForth and the AD9833
\ Copyright (C) 2021 Christopher Howard
\ SPDX-License-Identifier: GPL-3.0-or-later
\ This program 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 3 of the License, or
\ (at your option) any later version.
\ This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
\ The purpose of this file is to provide words for playing musical
\ notes from several octaves of the equal temperament musical tuning
\ system. Pitches are available from the 4th, 5th, and 6th octaves,
\ and note durations are available from a double-whole-note down to a
\ 16th note. The critical words for the user to be familiar with are
\ `init-durations', `play-note', and `rest', along with the associated
\ constants.
\ Note that the system provided here does not guarantee precise timing
\ and some notes will be at least a millisecond or two off in duration
\ compared to what they should be ideally. This is due to the
\ resolution of the duration calculations, as well as the fact that we
\ are using ms to wait instead of tracking ticks.
\ public words:
\ na na# nb nc nc# nd nd# ne nf nf# ng ng# o4 o5 o6 silence pitch-on
\ d_double d_dt-whole d_whole d_dt-half d_half d_dt-quarter d_quarter
\ d_dt-8th d_8th d_dt-16th d_16th init-durations play-note rest
\
\ **********************************************************************
notes
marker notes
\ **********************************************************************
\ This section contains a frequency data table and corresponding index
\ constants for three octaves. Freq register data is packed as follows
\ (lsb to msb numbering):
\ 0-13: the 14 LSBs of the lsb register load
\ 14-15: the 2 LSBs of the msb register load
\ **********************************************************************
flash
create packed-pitches
$1274 , $138d ,
$14b7 , $15f2 ,
$1740 , $18a2 ,
$1a19 , $1ba7 ,
$1d4c , $1f0a ,
$20e2 , $22d7 ,
$24e9 , $271b ,
$296e , $2be5 ,
$2e81 , $3145 ,
$3433 , $374d ,
$3a97 , $3e13 ,
$41c4 , $45ad ,
$49d2 , $4e36 ,
$52dc , $57c9 ,
$5d02 , $628a ,
$6866 , $6e9b ,
$752e , $7c26 ,
$8388 , $8b5a ,
ram
0 constant na
1 constant na#
2 constant nb
3 constant nc
4 constant nc#
5 constant nd
6 constant nd#
7 constant ne
8 constant nf
9 constant nf#
10 constant ng
11 constant ng#
0 constant o4
1 constant o5
2 constant o6
\ **********************************************************************
\ Code for enabling and silencing pitch output
\ **********************************************************************
: pull-pitch ( note octave -- u ) 12 * + cells packed-pitches + @ ;
: pull-14lsb ( u - u ) %0011111111111111 and ;
: pull-2msb ( u - u ) %1100000000000000 and 14 rshift ;
: tx-pitch ( note octave -- )
pull-pitch cp>r pull-14lsb FREG0_PF or 2tx-spi
r> pull-2msb FREG0_PF or 2tx-spi ;
: rst-load-16 ( -- )
[ 1 B28_BT lshift ] literal
[ 1 RESET_BT lshift ] literal or 2tx-spi ;
: pitch-on ( note octave -- )
fsync-low rst-load-16 tx-pitch 0 2tx-spi fsync-high ;
: silence ( -- )
fsync-low [ 1 RESET_BT lshift ] literal 2tx-spi fsync-high ;
\ **********************************************************************
\ The system which calculates and remembers the duration of different
\ note duration types, for example, whole notes and quarter notes. It
\ keeps track of ms duration values for each duration type, with one
\ value for how long the note should be held (sustain) and one value for
\ how long to rest afterwards (release).
\
\ These values are calculated based on what value is passed to
\ `init-durations', which should be the desired duration in milliseconds
\ of a whole note. `init-durations' must be run at least once after
\ every reset and before notes are played, since the calculated values
\ are stored in ram.
\
\ The `sustain-fract' and `release-fract' constants could be edited here
\ in the code to alter the sustain and release time calculations.
\ **********************************************************************
#00 constant d_double
#01 constant d_dt-whole
#02 constant d_whole
#03 constant d_dt-half
#04 constant d_half
#05 constant d_dt-quarter
#06 constant d_quarter
#07 constant d_dt-8th
#08 constant d_8th
#09 constant d_dt-16th
#10 constant d_16th
flash
create dur-fracts
#2 c, #1 c, \ d_double
#3 c, #2 c, \ d_dt-whole
#1 c, #1 c, \ d_whole
#3 c, #4 c, \ d_dt-half
#1 c, #2 c, \ d_half
#3 c, #8 c, \ d_dt-quarter
#1 c, #4 c, \ d_quarter
#3 c, #16 c, \ d_dt-8th
#1 c, #8 c, \ d_8th
#3 c, #32 c, \ d_dt-16th
#1 c, #16 c, \ d_16th
ram
7 8 2constant sustain-frac
1 8 2constant release-frac
: base-dur ( ms dur-type -- ms )
>r dur-fracts r@ ncell+ @ low-byte *
dur-fracts r> ncell+ @ high-byte / ;
: calc-sust-dur ( ms -- ms ) sustain-frac drop * sustain-frac nip / ;
: calc-rel-dur ( ms -- ms ) release-frac drop * release-frac nip / ;
create durations 22 cells allot
: init-durations ( ms -- )
11 0 do
dup i base-dur cp>r calc-sust-dur r> calc-rel-dur
durations i 2* ncell+ 2!
loop drop ;
: sustain-dur ( dur-type -- ms ) durations swap 2* ncell+ @ ;
: release-dur ( dur-type -- ms ) durations swap 2* 1 + ncell+ @ ;
\ **********************************************************************
\ Playing notes - pitch and duration in combination.
\ **********************************************************************
: play-note ( dur-type note octave -- )
pitch-on dup sustain-dur ms silence release-dur ms ;
: rest ( dur-type -- ) silence dup sustain-dur ms release-dur ms ;
\ **********************************************************************
\ The demo code completes the following steps:
\ 1. initializes spi communications with the ad9833
\ 2. sets the fsync pin to output mode
\ 3. sets the tempo to a 1000 ms whole-note
\ 4. nulls phase register 0
\ 5. plays a brief tune
\ **********************************************************************
: demo-play-note
init-spi init-fsync 2000 init-durations
fsync-low PHREG0_PF 2tx-spi fsync-high
d_quarter nc o4 play-note
d_quarter nd# o4 play-note
d_half ng o4 play-note
d_quarter rest
d_quarter nc o5 play-note
d_8th ng o4 play-note
d_8th nd# o4 play-note
d_half nc o4 play-note ;